DATAREST-774 - Separated integration tests from core project to avoid classpath overlap.

Extracted store specific tests into separate test modules to prevent classpath overlap between projects. Those tests are now executed in an "it" build profile to prevent the tests being packaged for distribution on release.

Use Map-based repositories and mapping contexts for test in the Core and WebMvc module.

Slightly changed the configuration API for lookup types on RepositoryRestConfiguration.

Related ticket: DATAREST-776.
This commit is contained in:
Oliver Gierke
2016-02-25 17:18:58 +01:00
parent 897bc88d69
commit 892409da2c
189 changed files with 1884 additions and 1506 deletions

View File

@@ -0,0 +1,46 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-parent</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests</name>
<artifactId>spring-data-rest-tests</artifactId>
<packaging>pom</packaging>
<modules>
<module>spring-data-rest-tests-core</module>
<module>spring-data-rest-tests-gemfire</module>
<module>spring-data-rest-tests-jpa</module>
<module>spring-data-rest-tests-mongodb</module>
<module>spring-data-rest-tests-security</module>
<module>spring-data-rest-tests-solr</module>
</modules>
<properties>
<groovy.version>2.4.4</groovy.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,55 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests - Core</name>
<artifactId>spring-data-rest-tests-core</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>${jsonpath}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,131 @@
package org.springframework.data.rest.tests;
/*
* Copyright 2013-2016 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.
*/
import java.util.Collections;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.support.DefaultSelfLinkProvider;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
import org.springframework.data.rest.webmvc.RootResourceInformation;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.support.Projector;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
/**
* Base class to write integration tests for controllers.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public abstract class AbstractControllerIntegrationTests {
public static final Path BASE = new Path("http://localhost");
@Configuration
public static class TestConfiguration extends RepositoryRestMvcConfiguration {
@Bean
public PersistentEntityResourceAssembler persistentEntityResourceAssembler() {
SelfLinkProvider selfLinkProvider = new DefaultSelfLinkProvider(persistentEntities(), entityLinks(),
Collections.<EntityLookup<?>> emptyList());
return new PersistentEntityResourceAssembler(persistentEntities(), StubProjector.INSTANCE, associationLinks(),
selfLinkProvider);
}
}
@Autowired Repositories repositories;
@Autowired RepositoryInvokerFactory invokerFactory;
@Autowired ResourceMappings mappings;
@Before
public void initWebInfrastructure() {
TestMvcClient.initWebTest();
}
/**
* Returns a {@link RootResourceInformation} for the given domain type.
*
* @param domainType must not be {@literal null}.
* @return
*/
protected RootResourceInformation getResourceInformation(Class<?> domainType) {
Assert.notNull(domainType, "Domain type must not be null!");
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
return new RootResourceInformation(mappings.getMetadataFor(domainType), entity,
invokerFactory.getInvokerFor(domainType));
}
protected WebRequest getRequest(RequestParameters parameters) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameters(parameters.asMap());
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
return new ServletWebRequest(request);
}
protected ResourceMetadata getMetadata(Class<?> domainType) {
return mappings.getMetadataFor(domainType);
}
private static enum StubProjector implements Projector {
INSTANCE;
@Override
public Object project(Object source) {
return source;
}
@Override
public Object projectExcerpt(Object source) {
return source;
}
@Override
public boolean hasExcerptProjection(Class<?> type) {
return false;
}
}
}

View File

@@ -0,0 +1,250 @@
package org.springframework.data.rest.tests;
/*
* Copyright 2013-2016 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.
*/
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Collections;
import java.util.Map;
import net.minidev.json.JSONArray;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
/**
* A test harness for hypermedia unit/integration testing. Provides chained operations (like postAndGet) to create a new
* entity and then retrieve it with a single method call. It also provides often-used assertions (like
* assertJsonPathEquals).
*
* @author Oliver Gierke
* @author Greg Turnquist
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = RepositoryRestMvcConfiguration.class)
public abstract class AbstractWebIntegrationTests {
private static final String CONTENT_LINK_JSONPATH = "$._embedded.._links.%s.href[0]";
@Autowired WebApplicationContext context;
@Autowired LinkDiscoverers discoverers;
protected TestMvcClient client;
protected MockMvc mvc;
@Before
public void setUp() {
setupMockMvc();
this.client = new TestMvcClient(mvc, discoverers);
}
protected void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).//
defaultRequest(get("/").accept(TestMvcClient.DEFAULT_MEDIA_TYPE)).build();
}
protected MockHttpServletResponse postAndGet(Link link, Object payload, MediaType mediaType) throws Exception {
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
MockHttpServletResponse response = mvc.perform(post(href).content(payload.toString()).contentType(mediaType)).//
andExpect(status().isCreated()).//
andExpect(header().string("Location", is(notNullValue()))).//
andReturn().getResponse();
String content = response.getContentAsString();
if (StringUtils.hasText(content)) {
return response;
}
return client.request(response.getHeader("Location"));
}
protected MockHttpServletResponse putAndGet(Link link, Object payload, MediaType mediaType) throws Exception {
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
MockHttpServletResponse response = mvc.perform(put(href).content(payload.toString()).contentType(mediaType)).//
andExpect(status().is2xxSuccessful()).//
andReturn().getResponse();
return StringUtils.hasText(response.getContentAsString()) ? response : client.request(link);
}
protected MockHttpServletResponse patchAndGet(Link link, Object payload, MediaType mediaType) throws Exception {
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
MockHttpServletResponse response = mvc.perform(MockMvcRequestBuilders.request(HttpMethod.PATCH, href).//
content(payload.toString()).contentType(mediaType)).//
andExpect(status().is2xxSuccessful()).//
andReturn().getResponse();
return StringUtils.hasText(response.getContentAsString()) ? response : client.request(href);
}
protected void deleteAndVerify(Link link) throws Exception {
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
mvc.perform(delete(href)).//
andExpect(status().isNoContent()).//
andReturn().getResponse();
// Check that the resource is unavailable after a DELETE
mvc.perform(get(href)).//
andExpect(status().isNotFound());
}
protected Link assertHasContentLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
return assertContentLinkWithRel(rel, response, true);
}
protected void assertDoesNotHaveContentLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
assertContentLinkWithRel(rel, response, false);
}
protected Link assertContentLinkWithRel(String rel, MockHttpServletResponse response, boolean expected)
throws Exception {
String content = response.getContentAsString();
try {
String href = JsonPath.read(content, String.format(CONTENT_LINK_JSONPATH, rel)).toString();
assertThat("Expected to find a link with rel" + rel + " in the content section of the response!", href,
is(expected ? notNullValue() : nullValue()));
return new Link(href, rel);
} catch (InvalidPathException o_O) {
if (expected) {
fail("Didn't find any content in the given response!");
}
return null;
}
}
protected void assertDoesNotHaveLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Link link = client.getDiscoverer(response).findLinkWithRel(rel, content);
assertThat("Expected not to find link with rel " + rel + " but found " + link + "!", link, is(nullValue()));
}
@SuppressWarnings("unchecked")
protected <T> T assertHasJsonPathValue(String path, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Object jsonPathResult = JsonPath.read(content, path);
assertThat(String.format("JSONPath lookup for %s did return null in %s.", path, content), jsonPathResult,
is(notNullValue()));
if (jsonPathResult instanceof JSONArray) {
JSONArray array = (JSONArray) jsonPathResult;
assertThat(array, hasSize(greaterThan(0)));
}
return (T) jsonPathResult;
}
protected void assertJsonPathDoesntExist(String path, MockHttpServletResponse response) throws Exception {
try {
Object result = JsonPath.read(response.getContentAsString(), path);
if (result != null) {
fail("Was expecting to find no value for path " + path + " but got " + result.toString());
}
} catch (InvalidPathException e) {}
}
protected String assertJsonPathEquals(String path, String expected, MockHttpServletResponse response)
throws Exception {
Object jsonQueryResults = assertHasJsonPathValue(path, response);
String jsonString = "";
if (jsonQueryResults instanceof JSONArray) {
jsonString = ((JSONArray) jsonQueryResults).toJSONString();
} else {
jsonString = jsonQueryResults != null ? jsonQueryResults.toString() : null;
}
assertThat(jsonString, is(expected));
return jsonString;
}
protected ResultMatcher doesNotHaveLinkWithRel(final String rel) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
MockHttpServletResponse response = result.getResponse();
String s = response.getContentAsString();
assertThat("Expected not to find link with rel " + rel + " but found one in " + s, //
client.getDiscoverer(response).findLinkWithRel(rel, s), nullValue());
}
};
}
protected Map<String, String> getPayloadToPost() throws Exception {
return Collections.emptyMap();
}
protected MultiValueMap<String, String> getRootAndLinkedResources() {
return new LinkedMultiValueMap<String, String>(0);
}
}

View File

@@ -0,0 +1,270 @@
package org.springframework.data.rest.tests;
/*
* Copyright 2013-2016 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.
*/
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import net.minidev.json.JSONArray;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import com.jayway.jsonpath.JsonPath;
/**
* This class contains a common test suite used to verify multiple data stores with the same domain space. When
* verifying support of a new data store, it's good to start with extending this suite of tests. However, if the data
* store doesn't map well onto this, then a good alternative would be write a new test suite using
* {@link org.springframework.data.rest.webmvc.AbstractWebIntegrationTests AbstractWebIntegrationTests} as the test
* harness.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
public abstract class CommonWebTests extends AbstractWebIntegrationTests {
protected abstract Iterable<String> expectedRootLinkRels();
// Root test cases
@Test
public void exposesRootResource() throws Exception {
ResultActions actions = mvc.perform(get("/").accept(TestMvcClient.DEFAULT_MEDIA_TYPE)).andExpect(status().isOk());
for (String rel : expectedRootLinkRels()) {
actions.andExpect(client.hasLinkWithRel(rel));
}
}
/**
* @see DATAREST-113
* @see DATAREST-638
*/
@Test
public void exposesSchemasForResourcesExposed() throws Exception {
MockHttpServletResponse response = client.request("/");
for (String rel : expectedRootLinkRels()) {
Link link = client.assertHasLinkWithRel(rel, response);
// Resource
client.follow(link).andExpect(status().is2xxSuccessful());
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());
}
}
/**
* @see DATAREST-203
*/
@Test
public void servesHalWhenRequested() throws Exception {
mvc.perform(get("/")). //
andExpect(content().contentType(MediaTypes.HAL_JSON)). //
andExpect(jsonPath("$._links", notNullValue()));
}
/**
* @see DATAREST-203
*/
@Test
public void servesHalWhenJsonIsRequested() throws Exception {
mvc.perform(get("/").accept(MediaType.APPLICATION_JSON)). //
andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)). //
andExpect(jsonPath("$._links", notNullValue()));
}
/**
* @see DATAREST-203
*/
@Test
public void exposesSearchesForRootResources() throws Exception {
MockHttpServletResponse response = client.request("/");
for (String rel : expectedRootLinkRels()) {
Link link = client.assertHasLinkWithRel(rel, response);
String rootResourceRepresentation = client.request(link).getContentAsString();
Link searchLink = client.getDiscoverer(response).findLinkWithRel("search", rootResourceRepresentation);
if (searchLink != null) {
client.follow(searchLink).//
andExpect(client.hasLinkWithRel("self")).//
andExpect(jsonPath("$.domainType", is(nullValue()))); // DATAREST-549
}
}
}
@Test
public void nic() throws Exception {
Map<String, String> payloads = getPayloadToPost();
assumeFalse(payloads.isEmpty());
MockHttpServletResponse response = client.request("/");
for (String rel : expectedRootLinkRels()) {
String payload = payloads.get(rel);
if (payload != null) {
Link link = client.assertHasLinkWithRel(rel, response);
String target = link.expand().getHref();
MockHttpServletRequestBuilder request = post(target).//
content(payload).//
contentType(MediaType.APPLICATION_JSON);
mvc.perform(request). //
andExpect(status().isCreated());
}
}
}
/**
* @see DATAREST-198
*/
@Test
public void accessLinkedResources() throws Exception {
MockHttpServletResponse rootResource = client.request("/");
for (Map.Entry<String, List<String>> linked : getRootAndLinkedResources().entrySet()) {
Link resourceLink = client.assertHasLinkWithRel(linked.getKey(), rootResource);
MockHttpServletResponse resource = client.request(resourceLink);
for (String linkedRel : linked.getValue()) {
// Find URIs pointing to linked resources
String jsonPath = String.format("$..%s._links.%s.href", linked.getKey(), linkedRel);
String representation = resource.getContentAsString();
JSONArray uris = JsonPath.read(representation, jsonPath);
for (Object href : uris) {
client.follow(href.toString()). //
andExpect(status().isOk());
}
}
}
}
/**
* @see DATAREST-230
*/
@Test
public void exposesDescriptionAsAlpsDocuments() throws Exception {
MediaType ALPS_MEDIA_TYPE = MediaType.valueOf("application/alps+json");
MockHttpServletResponse response = client.request("/");
Link profileLink = client.assertHasLinkWithRel("profile", response);
mvc.perform(//
get(profileLink.expand().getHref()).//
accept(ALPS_MEDIA_TYPE))
.//
andExpect(status().isOk()).//
andExpect(content().contentType(ALPS_MEDIA_TYPE));
}
/**
* @see DATAREST-448
*/
@Test
public void returnsNotFoundForUriNotBackedByARepository() throws Exception {
mvc.perform(get("/index.html")).//
andExpect(status().isNotFound());
}
/**
* @see DATAREST-658
*/
@Test
public void collectionResourcesExposeLinksAsHeadersForHeadRequest() throws Exception {
for (String rel : expectedRootLinkRels()) {
Link link = client.discoverUnique(rel);
MockHttpServletResponse response = mvc.perform(head(link.expand().getHref()))//
.andExpect(status().isNoContent())//
.andReturn().getResponse();
Links links = Links.valueOf(response.getHeader("Link"));
assertThat(links.hasLink(Link.REL_SELF), is(true));
assertThat(links.hasLink("profile"), is(true));
}
}
/**
* @see DATAREST-661
*/
@Test
public void patchToNonExistingResourceReturnsNotFound() throws Exception {
String rel = expectedRootLinkRels().iterator().next();
String uri = client.discoverUnique(rel).expand().getHref().concat("/");
String id = "4711";
Integer status = null;
do {
// Try to find non existing resource
uri = uri.concat(id);
status = mvc.perform(get(URI.create(uri))).andReturn().getResponse().getStatus();
} while (status != HttpStatus.NOT_FOUND.value());
// PATCH to non-existing resource
mvc.perform(patch(URI.create(uri))).andExpect(status().isNotFound());
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2012-2016 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.tests;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
import org.springframework.data.rest.core.config.MetadataConfiguration;
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.support.DefaultSelfLinkProvider;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
import org.springframework.data.rest.webmvc.ResourceProcessorInvoker;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.mapping.LinkCollector;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
import org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Jon Brisbin
* @author Greg Turnquist
* @author Oliver Gierke
*/
@Configuration
public class RepositoryTestsConfig {
@Autowired ApplicationContext appCtx;
@Autowired(required = false) List<MappingContext<?, ?>> mappingContexts = Collections.emptyList();
@Bean
public Repositories repositories() {
return new Repositories(appCtx);
}
@Bean
public RepositoryRestConfiguration config() {
return new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(), new MetadataConfiguration(),
mock(EnumTranslationConfiguration.class));
}
@Bean
public DefaultFormattingConversionService defaultConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
DomainClassConverter<FormattingConversionService> converter = new DomainClassConverter<FormattingConversionService>(
conversionService);
converter.setApplicationContext(appCtx);
return conversionService;
}
@Bean
public PersistentEntities persistentEntities() {
return new PersistentEntities(mappingContexts);
}
@Bean
public Module persistentEntityModule() {
RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(),
config().getRepositoryDetectionStrategy());
EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(),
mock(PagingAndSortingTemplateVariables.class),
OrderAwarePluginRegistry.<Class<?>, BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE)));
SelfLinkProvider selfLinkProvider = new DefaultSelfLinkProvider(persistentEntities(), entityLinks,
Collections.<EntityLookup<?>> emptyList());
DefaultRepositoryInvokerFactory invokerFactory = new DefaultRepositoryInvokerFactory(repositories());
UriToEntityConverter uriToEntityConverter = new UriToEntityConverter(persistentEntities(), invokerFactory,
repositories());
Associations associations = new Associations(mappings, config());
LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations);
NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities(),
new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class)),
new ResourceProcessorInvoker(Collections.<ResourceProcessor<?>> emptyList()));
return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector,
invokerFactory, nestedEntitySerializer, mock(LookupObjectSerializer.class));
}
@Bean
public ObjectMapper objectMapper() {
RelProvider relProvider = new EvoInflectorRelProvider();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.registerModule(persistentEntityModule());
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null));
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_EMPTY);
return mapper;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2016 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.tests;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
public class RequestParameters {
public static RequestParameters NONE = new RequestParameters();
private final Map<String, String[]> parameters;
public RequestParameters(String key, String... values) {
this(new HashMap<String, String[]>(), key, values);
}
private RequestParameters(Map<String, String[]> parameters, String key, String... values) {
Assert.notNull(parameters, "Parameters must not be null!");
Assert.hasText(key, "Key must not be null or empty!");
this.parameters = new HashMap<String, String[]>(parameters);
this.parameters.put(key, values);
}
private RequestParameters() {
this.parameters = new HashMap<String, String[]>();
}
public RequestParameters and(String key, String... values) {
return new RequestParameters(parameters, key, values);
}
public Map<String, String[]> asMap() {
return Collections.unmodifiableMap(parameters);
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2013-2016 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.tests;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.hamcrest.Matcher;
import org.springframework.data.rest.core.Path;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.util.Assert;
import org.springframework.web.util.UriTemplate;
/**
* Simple wrapper for {@link Resource}s to allow easy assertions on it.
*
* @author Oliver Gierke
*/
public class ResourceTester {
private final ResourceSupport resource;
public static ResourceTester of(Object object) {
assertThat(object, is(instanceOf(ResourceSupport.class)));
return new ResourceTester((ResourceSupport) object);
}
/**
* Creates a new {@link ResourceTester} for the given {@link ResourceSupport}.
*
* @param resource must not be {@literal null}.
*/
private ResourceTester(ResourceSupport resource) {
Assert.notNull(resource, "Resource must not be null!");
this.resource = resource;
}
/**
* Asserts that the {@link Resource} contains the given number of {@link Link}s.
*
* @param number
*/
public void assertNumberOfLinks(int number) {
assertThat(resource.getLinks().size(), is(number));
}
/**
* Asserts that the {@link Resource} has a link with the given rel and href.
*
* @param rel must not be {@literal null}.
* @param href can be {@literal null}, if so, only the presence of a {@link Link} with the given rel is checked.
*/
public Link assertHasLink(String rel, String href) {
return assertHasLinkMatching(rel, href == null ? null : is(href));
}
/**
* Asserts that the {@link Resource} has a link with the given rel and ending with the given href.
*
* @param rel must not be {@literal null}.
* @param href can be {@literal null}, if so, only the presence of a {@link Link} with the given rel is checked.
*/
public Link assertHasLinkEndingWith(String rel, String hrefEnd) {
return assertHasLinkMatching(rel, hrefEnd == null ? null : endsWith(hrefEnd));
}
private final Link assertHasLinkMatching(String rel, Matcher<String> hrefMatcher) {
Link link = resource.getLink(rel);
assertThat("Expected link with rel '" + rel + "' but didn't find it in " + resource.getLinks(), link,
is(notNullValue()));
if (hrefMatcher != null) {
assertThat(link.getHref(), is(hrefMatcher));
}
return link;
}
@SuppressWarnings("unchecked")
public <T> PagedResources<T> assertIsPage() {
assertThat(resource, is(instanceOf(PagedResources.class)));
return (PagedResources<T>) resource;
}
public ResourceTester getContentResource() {
assertThat(resource, is(instanceOf(Resources.class)));
Object next = ((Resources<?>) resource).getContent().iterator().next();
assertThat(next, is(instanceOf(ResourceSupport.class)));
return new ResourceTester((ResourceSupport) next);
}
public void withContentResource(ContentResourceHandler handler) {
assertThat(resource, is(instanceOf(Resources.class)));
for (Object element : ((Resources<?>) resource).getContent()) {
assertThat(element, is(instanceOf(ResourceSupport.class)));
handler.doWith(of(element));
}
}
public interface ContentResourceHandler {
void doWith(ResourceTester content);
}
public static class HasSelfLink implements ContentResourceHandler {
private final Path template;
public HasSelfLink(Path template) {
this.template = template;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.ResourceTester.ContentResourceHandler#doWith(org.springframework.data.rest.webmvc.ResourceTester)
*/
@Override
public void doWith(ResourceTester content) {
String href = content.assertHasLink("self", null).getHref();
UriTemplate uriTemplate = new UriTemplate(template.toString());
assertThat(String.format("Expected %s to match %s!", href, uriTemplate.toString()), uriTemplate.matches(href),
is(true));
}
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright 2013-2016 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.tests;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* Helper methods for web integration testing.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
public class TestMvcClient {
public static MediaType DEFAULT_MEDIA_TYPE = org.springframework.hateoas.MediaTypes.HAL_JSON;
private final MockMvc mvc;
private final LinkDiscoverers discoverers;
/**
* Creates a new {@link TestMvcClient} for the given {@link MockMvc} and {@link LinkDiscoverers}.
*
* @param mvc must not be {@literal null}.
* @param discoverers must not be {@literal null}.
*/
public TestMvcClient(MockMvc mvc, LinkDiscoverers discoverers) {
Assert.notNull(mvc, "MockMvc must not be null!");
Assert.notNull(discoverers, "LinkDiscoverers must not be null!");
this.mvc = mvc;
this.discoverers = discoverers;
}
/**
* Initializes web tests. Will register a {@link MockHttpServletRequest} for the current thread.
*/
public static void initWebTest() {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
public static void assertAllowHeaders(HttpEntity<?> response, HttpMethod... methods) {
HttpHeaders headers = response.getHeaders();
assertThat(headers.getAllow(), hasSize(methods.length));
assertThat(headers.getAllow(), hasItems(methods));
}
/**
* Perform GET [href] with an explicit Accept media type using MockMvc. Verify the requests succeeded and also came
* back as the Accept type.
*
* @param href
* @param contentType
* @return a mocked servlet response with results from GET [href]
* @throws Exception
*/
public MockHttpServletResponse request(String href, MediaType contentType) throws Exception {
return mvc.perform(get(href).accept(contentType)). //
andExpect(status().isOk()). //
andExpect(content().contentType(contentType)). //
andReturn().getResponse();
}
/**
* Perform GET [href] with an explicit Accept media type using MockMvc. Verify the requests succeeded and also came
* back as the Accept type.
*
* @param href
* @param contentType
* @return a mocked servlet response with results from GET [href]
* @throws Exception
*/
public MockHttpServletResponse request(String href, MediaType contentType, HttpHeaders httpHeaders) throws Exception {
return mvc.perform(get(href).accept(contentType).headers(httpHeaders)). //
andExpect(status().isOk()). //
andExpect(content().contentType(contentType)). //
andReturn().getResponse();
}
/**
* Convenience wrapper that first expands the link using URI substitution before requesting with the default media
* type.
*
* @param link
* @return
* @throws Exception
*/
public MockHttpServletResponse request(Link link) throws Exception {
return request(link.expand().getHref());
}
/**
* Convenience wrapper that first expands the link using URI substitution and then GET [href] using an explicit media
* type
*
* @param link
* @param mediaType
* @return
* @throws Exception
*/
public MockHttpServletResponse request(Link link, MediaType mediaType) throws Exception {
return request(link.expand().getHref(), mediaType);
}
/**
* Convenience wrapper to GET [href] using the default media type.
*
* @param href
* @return
* @throws Exception
*/
public MockHttpServletResponse request(String href) throws Exception {
return request(href, DEFAULT_MEDIA_TYPE);
}
/**
* For a given link, expand the href using URI substitution and then do a simple GET.
*
* @param link
* @return
* @throws Exception
*/
public ResultActions follow(Link link) throws Exception {
return follow(link.expand().getHref());
}
/**
* Follow URL supplied as a string. NOTE: Assumes no URI templates.
*
* @param href
* @return
* @throws Exception
*/
public ResultActions follow(String href) throws Exception {
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()));
}
/**
* Discover list of URIs associated with a rel, starting at the root node ("/")
*
* @param rel
* @return
* @throws Exception
*/
public List<Link> discover(String rel) throws Exception {
return discover(new Link("/"), rel);
}
/**
* Discover single URI associated with a rel, starting at the root node ("/")
*
* @param rel
* @return
* @throws Exception
*/
public Link discoverUnique(String rel) throws Exception {
List<Link> discover = discover(rel);
assertThat(discover, hasSize(1));
return discover.get(0);
}
/**
* Traverses the given link relations from the root.
*
* @param rels
* @return
* @throws Exception
*/
public Link discoverUnique(String... rels) throws Exception {
Iterator<String> toTraverse = Arrays.asList(rels).iterator();
Link lastLink = null;
while (toTraverse.hasNext()) {
String rel = toTraverse.next();
lastLink = lastLink == null ? discoverUnique(rel) : discoverUnique(lastLink, rel);
}
return lastLink;
}
/**
* Given a URI (root), discover the URIs for a given rel.
*
* @param root - URI to start from
* @param rel - name of the relationship to seek links
* @return list of {@link org.springframework.hateoas.Link Link} objects associated with the rel
* @throws Exception
*/
public List<Link> discover(Link root, String rel) throws Exception {
MockHttpServletResponse response = mvc.perform(get(root.expand().getHref()).accept(DEFAULT_MEDIA_TYPE)).//
andExpect(status().isOk()).//
andExpect(hasLinkWithRel(rel)).//
andReturn().getResponse();
String s = response.getContentAsString();
return getDiscoverer(response).findLinksWithRel(rel, s);
}
/**
* Given a URI (root), discover the unique URI for a given rel. NOTE: Assumes there is only one URI
*
* @param root
* @param rel
* @return {@link org.springframework.hateoas.Link Link} tied to a given rel
* @throws Exception
*/
public Link discoverUnique(Link root, String rel) throws Exception {
return discoverUnique(root, rel, DEFAULT_MEDIA_TYPE);
}
/**
* Given a URI (root), discover the unique URI for a given rel. NOTE: Assumes there is only one URI
*
* @param root the link to the resource to access.
* @param rel the link relation to discover in the response.
* @param mediaType the {@link MediaType} to request.
* @return {@link org.springframework.hateoas.Link Link} tied to a given rel
* @throws Exception
*/
public Link discoverUnique(Link root, String rel, MediaType mediaType) throws Exception {
MockHttpServletResponse response = mvc
.perform(get(root.expand().getHref())//
.accept(mediaType))
.andExpect(status().isOk())//
.andExpect(hasLinkWithRel(rel))//
.andReturn().getResponse();
return assertHasLinkWithRel(rel, response);
}
/**
* For a given servlet response, verify that the provided rel exists in its hypermedia. If so, return the URI link.
*
* @param rel
* @param response
* @return {@link org.springframework.hateoas.Link} of the rel found in the response
* @throws Exception
*/
public Link assertHasLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Link link = getDiscoverer(response).findLinkWithRel(rel, content);
assertThat("Expected to find link with rel " + rel + " but found none in " + content + "!", link,
is(notNullValue()));
return link;
}
/**
* MockMvc matcher used to verify existence of rel with URI link
*
* @param rel
* @return
*/
public ResultMatcher hasLinkWithRel(final String rel) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
MockHttpServletResponse response = result.getResponse();
String s = response.getContentAsString();
assertThat("Expected to find link with rel " + rel + " but found none in " + s, //
getDiscoverer(response).findLinkWithRel(rel, s), notNullValue());
}
};
}
/**
* Using the servlet response's content type, find the corresponding link discoverer.
*
* @param response
* @return {@link org.springframework.hateoas.LinkDiscoverer}
*/
public LinkDiscoverer getDiscoverer(MockHttpServletResponse response) {
String contentType = response.getContentType();
LinkDiscoverer linkDiscovererFor = discoverers.getLinkDiscovererFor(contentType);
assertThat("Did not find a LinkDiscoverer for returned media type " + contentType + "!", linkDiscovererFor,
is(notNullValue()));
return linkDiscovererFor;
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="warn" />
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>

View File

@@ -0,0 +1,34 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests - Gemfire</name>
<artifactId>spring-data-rest-tests-gemfire</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests-core</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<type>test-jar</type>
</dependency>
<!-- Gemfire -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
<version>${springdata.gemfire}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import org.springframework.data.annotation.Id;
/**
* Base class for persistent classes.
*
* @author Oliver Gierke
* @author David Turanski
*/
public class AbstractPersistentEntity {
@Id private final Long id;
/**
* Returns the identifier of the entity.
*
* @return the id
*/
public Long getId() {
return id;
}
protected AbstractPersistentEntity(Long id) {
this.id = id;
}
protected AbstractPersistentEntity() {
this.id = null;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;
}
AbstractPersistentEntity that = (AbstractPersistentEntity) obj;
return this.id.equals(that.getId());
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import org.springframework.util.Assert;
/**
* An address.
*
* @author Oliver Gierke
*/
public class Address {
private final String street, city, country;
/**
* Creates a new {@link Address} from the given street, city and country.
*
* @param street must not be {@literal null} or empty.
* @param city must not be {@literal null} or empty.
* @param country must not be {@literal null} or empty.
*/
public Address(String street, String city, String country) {
Assert.hasText(street, "Street must not be null or empty!");
Assert.hasText(city, "City must not be null or empty!");
Assert.hasText(country, "Country must not be null or empty!");
this.street = street;
this.city = city;
this.country = country;
}
/**
* Returns a copy of the current {@link Address} instance which is a new entity in terms of persistence.
*
* @return
*/
public Address getCopy() {
return new Address(this.street, this.city, this.country);
}
/**
* Returns the street.
*
* @return
*/
public String getStreet() {
return street;
}
/**
* Returns the city.
*
* @return
*/
public String getCity() {
return city;
}
/**
* Returns the country.
*
* @return
*/
public String getCountry() {
return country;
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.util.Assert;
/**
* A customer.
*
* @author Oliver Gierke
* @author David Turanski
*/
@Region
public class Customer extends AbstractPersistentEntity {
private EmailAddress emailAddress;
private String firstname, lastname;
private Set<Address> addresses = new HashSet<Address>();
/**
* Creates a new {@link Customer} from the given parameters.
*
* @param id the unique id;
* @param emailAddress must not be {@literal null} or empty.
* @param firstname must not be {@literal null} or empty.
* @param lastname must not be {@literal null} or empty.
*/
public Customer(Long id, EmailAddress emailAddress, String firstname, String lastname) {
super(id);
Assert.hasText(firstname);
Assert.hasText(lastname);
Assert.notNull(emailAddress);
this.firstname = firstname;
this.lastname = lastname;
this.emailAddress = emailAddress;
}
protected Customer() {}
/**
* Adds the given {@link Address} to the {@link Customer}.
*
* @param address must not be {@literal null}.
*/
public void add(Address address) {
Assert.notNull(address);
this.addresses.add(address);
}
/**
* Returns the firstname of the {@link Customer}.
*
* @return
*/
public String getFirstname() {
return firstname;
}
/**
* Sets the firstname of the {@link Customer}.
*
* @param firstname
*/
public void setFirstname(String firstname) {
this.firstname = firstname;
}
/**
* Returns the lastname of the {@link Customer}.
*
* @return
*/
public String getLastname() {
return lastname;
}
/**
* Sets the lastname of the {@link Customer}.
*
* @param lastname
*/
public void setLastname(String lastname) {
this.lastname = lastname;
}
/**
* Returns the {@link EmailAddress} of the {@link Customer}.
*
* @return
*/
public EmailAddress getEmailAddress() {
return emailAddress;
}
/**
* Sets the emailAddress of the {@link Customer}.
*
* @param emailAddress
*/
public void setEmailAddress(EmailAddress emailAddress) {
this.emailAddress = emailAddress;
}
/**
* Return the {@link Customer}'s addresses.
*
* @return
*/
public Set<Address> getAddresses() {
return Collections.unmodifiableSet(addresses);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
/**
* Repository interface to access {@link Customer}s.
*
* @author Oliver Gierke
* @author David Turanski
*/
public interface CustomerRepository extends CrudRepository<Customer, Long> {
/**
* Finds all {@link Customer}s with the given lastname.
*
* @param lastname
* @return
*/
List<Customer> findByLastname(@Param("lastname") String lastname);
/**
* Finds the Customer with the given {@link EmailAddress}.
*
* @param emailAddress
* @return
*/
Customer findByEmailAddress(@Param("email") EmailAddress emailAddress);
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.util.regex.Pattern;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
/**
* Value object to represent email addresses.
*
* @author Oliver Gierke
*/
@JsonSerialize(using = ToStringSerializer.class)
public final class EmailAddress {
private static final String EMAIL_REGEX = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private static final Pattern PATTERN = Pattern.compile(EMAIL_REGEX);
private final String value;
/**
* Creates a new {@link EmailAddress} from the given {@link String} representation.
*
* @param emailAddress must not be {@literal null} or empty.
*/
@JsonCreator
public EmailAddress(String emailAddress) {
Assert.isTrue(isValid(emailAddress), "Invalid email address!");
this.value = emailAddress;
}
/**
* Returns whether the given {@link String} is a valid {@link EmailAddress} which means you can safely instantiate the
* class.
*
* @param candidate
* @return
*/
public static boolean isValid(String candidate) {
return candidate == null ? false : PATTERN.matcher(candidate).matches();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return value;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof EmailAddress)) {
return false;
}
EmailAddress that = (EmailAddress) obj;
return this.value.equals(that.value);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return value.hashCode();
}
@Component
static class EmailAddressToStringConverter implements Converter<EmailAddress, String> {
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public String convert(EmailAddress source) {
return source == null ? null : source.value;
}
}
@Component
static class StringToEmailAddressConverter implements Converter<String, EmailAddress> {
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
public EmailAddress convert(String source) {
return StringUtils.hasText(source) ? new EmailAddress(source) : null;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.math.BigDecimal;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
public class LineItem {
private BigDecimal price;
private int amount;
private Long productId;
/**
* Creates a new {@link LineItem} for the given {@link Product}.
*
* @param product must not be {@literal null}.
*/
public LineItem(Product product) {
this(product, 1);
}
/**
* Creates a new {@link LineItem} for the given {@link Product} and amount.
*
* @param product must not be {@literal null}.
* @param amount
*/
public LineItem(Product product, int amount) {
Assert.notNull(product, "The given Product must not be null!");
Assert.isTrue(amount > 0, "The amount of Products to be bought must be greater than 0!");
this.productId = product.getId();
this.amount = amount;
this.price = product.getPrice();
}
protected LineItem() {}
/**
* Returns the id of the {@link Product} the {@link LineItem} refers to.
*
* @return
*/
public Long getProductId() {
return productId;
}
/**
* Returns the amount of {@link Product}s to be ordered.
*
* @return
*/
public int getAmount() {
return amount;
}
/**
* Returns the price a single unit of the {@link LineItem}'s product.
*
* @return the price
*/
public BigDecimal getUnitPrice() {
return price;
}
/**
* Returns the total for the {@link LineItem}.
*
* @return
*/
public BigDecimal getTotal() {
return price.multiply(BigDecimal.valueOf(amount));
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
* @author David Turanski
*/
@Region
public class Order extends AbstractPersistentEntity {
private Long customerId;
private Address billingAddress;
private Address shippingAddress;
private Set<LineItem> lineItems = new HashSet<LineItem>();
/**
* Creates a new {@link Order} for the given {@link org.springframework.data.rest.tests.gemfire.Customer}.
*
* @param id order ID
* @param customerId must not be {@literal null}.
* @param shippingAddress must not be {@literal null}.
*/
public Order(Long id, Long customerId, Address shippingAddress) {
super(id);
Assert.notNull(customerId);
Assert.notNull(shippingAddress);
this.customerId = customerId;
this.shippingAddress = shippingAddress;
}
protected Order() {}
/**
* Adds the given {@link LineItem} to the {@link Order}.
*
* @param lineItem
*/
public void add(LineItem lineItem) {
this.lineItems.add(lineItem);
}
/**
* Returns the id of the {@link org.springframework.data.rest.tests.gemfire.Customer} who placed the
* {@link Order}.
*
* @return
*/
public Long getCustomerId() {
return customerId;
}
/**
* Returns the billing {@link Address} for this order.
*
* @return
*/
public Address getBillingAddress() {
return billingAddress != null ? billingAddress : shippingAddress;
}
/**
* Returns the shipping {@link Address} for this order;
*
* @return
*/
public Address getShippingAddress() {
return shippingAddress;
}
/**
* Returns all {@link LineItem}s currently belonging to the {@link Order}.
*
* @return
*/
public Set<LineItem> getLineItems() {
return Collections.unmodifiableSet(lineItems);
}
/**
* Returns the total of the {@link Order}.
*
* @return
*/
public BigDecimal getTotal() {
BigDecimal total = BigDecimal.ZERO;
for (LineItem item : lineItems) {
total = total.add(item.getTotal());
}
return total;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
/**
* @author Oliver Gierke
* @author David Turanski
*/
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.util.Assert;
/**
* A product.
*
* @author Oliver Gierke
* @author David Turanski
*/
@Region
public class Product extends AbstractPersistentEntity {
private String name, description;
private BigDecimal price;
private Map<String, String> attributes = new HashMap<String, String>();
/**
* Creates a new {@link Product} with the given name.
*
* @param id a unique Id
* @param name must not be {@literal null} or empty.
* @param price must not be {@literal null} or less than or equal to zero.
*/
public Product(Long id, String name, BigDecimal price) {
this(id, name, price, null);
}
/**
* Creates a new {@link Product} from the given name and description.
*
* @param id a unique Id
* @param name must not be {@literal null} or empty.
* @param price must not be {@literal null} or less than or equal to zero.
* @param description
*/
@PersistenceConstructor
public Product(Long id, String name, BigDecimal price, String description) {
super(id);
Assert.hasText(name, "Name must not be null or empty!");
Assert.isTrue(BigDecimal.ZERO.compareTo(price) < 0, "Price must be greater than zero!");
this.name = name;
this.price = price;
this.description = description;
}
protected Product() {}
/**
* Sets the attribute with the given name to the given value.
*
* @param name must not be {@literal null} or empty.
* @param value
*/
public void setAttribute(String name, String value) {
Assert.hasText(name);
if (value == null) {
this.attributes.remove(value);
} else {
this.attributes.put(name, value);
}
}
/**
* Returns the {@link Product}'s name.
*
* @return
*/
public String getName() {
return name;
}
/**
* Returns the {@link Product}'s description.
*
* @return
*/
public String getDescription() {
return description;
}
/**
* Returns all the custom attributes of the {@link Product}.
*
* @return
*/
public Map<String, String> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
/**
* Returns the price of the {@link Product}.
*
* @return
*/
public BigDecimal getPrice() {
return price;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import java.util.List;
import org.springframework.data.gemfire.repository.Query;
import org.springframework.data.repository.CrudRepository;
/**
* Repository interface to access {@link Product}s.
*
* @author Oliver Gierke
* @author David Turanski
*/
public interface ProductRepository extends CrudRepository<Product, Long> {
/**
* Returns a list of {@link Product}s having a description which contains the given snippet.
*
* @param the search string
* @return
*/
List<Product> findByDescriptionContaining(String description);
/**
* Returns all {@link Product}s having the given attribute value.
*
* @param attribute
* @param value
* @return
*/
@Query("SELECT * FROM /Product where attributes[$1] = $2")
List<Product> findByAttributes(String key, String value);
List<Product> findByName(String name);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2016 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.tests.gemfire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
import org.springframework.data.util.AnnotatedTypeScanner;
/**
* Spring JavaConfig configuration class to setup a Spring container and infrastructure components.
*
* @author Oliver Gierke
* @author David Turanski
*/
@Configuration
@ImportResource("classpath:META-INF/spring/cache-config.xml")
@EnableGemfireRepositories
public class GemfireRepositoryConfig {
/**
* TODO: Remove, once Spring Data Gemfire exposes a mapping context.
*/
@Bean
@SuppressWarnings("unchecked")
public GemfireMappingContext gemfireMappingContext() {
AnnotatedTypeScanner scanner = new AnnotatedTypeScanner(Region.class);
GemfireMappingContext context = new GemfireMappingContext();
context.setInitialEntitySet(scanner.findTypes(GemfireRepositoryConfig.class.getPackage().getName()));
context.initialize();
return context;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2016 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.tests.gemfire;
import java.util.Arrays;
import org.springframework.data.rest.tests.CommonWebTests;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Oliver Gierke
*/
@ContextConfiguration(classes = GemfireRepositoryConfig.class)
public class GemfireWebTests extends CommonWebTests {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#expectedRootLinkRels()
*/
@Override
protected Iterable<String> expectedRootLinkRels() {
return Arrays.asList("products");
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gfe="http://www.springframework.org/schema/gemfire"
xsi:schemaLocation="http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<gfe:cache use-bean-factory-locator="false"/>
<gfe:replicated-region id="Customer"/>
<gfe:replicated-region id="Order"/>
<gfe:replicated-region id="Product"/>
</beans>

View File

@@ -0,0 +1,66 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests - JPA</name>
<artifactId>spring-data-rest-tests-jpa</artifactId>
<properties>
<spring-security.version>4.0.1.RELEASE</spring-security.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests-core</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<type>test-jar</type>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${springdata.jpa}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
<scope>test</scope>
</dependency>
<!-- Jackson Hibernate -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2014-2016 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.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
/**
* @author Oliver Gierke
*/
@Entity
public class Address {
public @Id @GeneratedValue Long id;
public @Version Long version;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2014-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* @author Oliver Gierke
*/
public interface AddressRepository extends CrudRepository<Address, Long> {
@Override
@RestResource(exported = false)
Iterable<Address> findAll();
@Override
@RestResource(exported = false)
<S extends Address> S save(S entity);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
/**
* @author Oliver Gierke
*/
@Entity
public class Author {
@Id @GeneratedValue//
Long id;
public String name;
@ManyToMany(mappedBy = "authors")//
public Set<Book> books = new HashSet<Book>();
protected Author() {}
public Author(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* @author Oliver Gierke
*/
public interface AuthorRepository extends CrudRepository<Author, Long> {
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2014-2016 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.jpa;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Sample custom controller to test the ability to override
*
* @author Oliver Gierke
*/
@RepositoryRestController
public class AuthorsController {
@RequestMapping(value = "/authors/{author}", method = RequestMethod.DELETE)
HttpEntity<?> deleteAuthor(@PathVariable Author author) {
Assert.notNull(author, "Author must not be null!");
return new ResponseEntity<Object>(HttpStatus.I_AM_A_TEAPOT);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* @author Oliver Gierke
*/
@Entity
public class Book {
public @Id @GeneratedValue Long id;
public String isbn, title;
@ManyToMany(cascade = { CascadeType.MERGE }) //
@RestResource(path = "creators") //
public Set<Author> authors;
protected Book() {}
public Book(String isbn, String title, Iterable<Author> authors) {
this.isbn = isbn;
this.title = title;
this.authors = new HashSet<Author>();
for (Author author : authors) {
author.books.add(this);
this.authors.add(author);
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014-2016 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.jpa;
import org.springframework.data.rest.core.config.Projection;
/**
* Interface for an excerpt projection for {@link Book}s.
*
* @author Oliver Gierke
*/
@Projection(name = "excerpt", types = Book.class)
public interface BookExcerpt {
String getTitle();
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2014-2016 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.jpa;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.util.StringUtils;
/**
* {@link BackendIdConverter} artificially transforming the actual book id into some magic {@link String} and back.
*
* @author Oliver Gierke
*/
public class BookIdConverter implements BackendIdConverter {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.support.BackendIdConverter#fromRequestId(java.lang.String, java.lang.Class)
*/
@Override
public Serializable fromRequestId(String id, Class<?> entityType) {
return Long.parseLong(id.substring(0, id.indexOf('-')));
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.support.BackendIdConverter#toRequestId(java.lang.Object, java.lang.Class)
*/
@Override
public String toRequestId(Serializable id, Class<?> entityType) {
Long longId = (Long) id;
List<Long> ids = new ArrayList<Long>(longId.intValue());
for (int i = 0; i < longId; i++) {
ids.add(longId);
}
return StringUtils.collectionToDelimitedString(ids, "-");
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Class<?> delimiter) {
return Book.class.equals(delimiter);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* @author Oliver Gierke
*/
@RepositoryRestResource(excerptProjection = BookExcerpt.class)
public interface BookRepository extends CrudRepository<Book, Long> {
@RestResource(rel = "find-by-sorted")
List<Book> findBy(Sort sort);
@Query("select b from Book b where :author member of b.authors")
List<Book> findByAuthorsContains(@Param("author") Author author);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2016 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.jpa;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Oliver Gierke
*/
@Entity
public class CreditCard {
@Id Long id;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* @author Oliver Gierke
*/
interface CreditCardRepository extends CrudRepository<CreditCard, Long> {
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2015-2016 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.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-463
*/
@Entity
public class Item {
private @Id @GeneratedValue Long id;
private String name;
private @JsonIgnore @OneToOne User owner;
private @OneToOne User manager, curator;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
@JsonIgnore
public User getManager() {
return manager;
}
public void setManager(User manager) {
this.manager = manager;
}
public User getCurator() {
return curator;
}
public void setCurator(User curator) {
this.curator = curator;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2015-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-463
*/
public interface ItemRepository extends CrudRepository<Item, Long> {}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2016 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.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Oliver Gierke
*/
@Entity
public class LineItem {
@Id @GeneratedValue//
private Long id;
private String name;
public LineItem(String name) {
this.name = name;
}
protected LineItem() {
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2013-2016 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.jpa;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* @author Oliver Gierke
*/
@Entity
@Table(name = "ORDERS")
public class Order {
@Id @GeneratedValue//
private Long id;
@ManyToOne(fetch = FetchType.LAZY)//
private Person creator;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)//
private List<LineItem> lineItems = new ArrayList<LineItem>();
private Type type = Type.TAKE_AWAY;
public Order(Person creator) {
this.creator = creator;
}
protected Order() {
}
public Long getId() {
return id;
}
public Person getCreator() {
return creator;
}
/**
* @return the lineItems
*/
public List<LineItem> getLineItems() {
return lineItems;
}
public void add(LineItem item) {
this.lineItems.add(item);
}
public BigDecimal getPrice() {
return new BigDecimal(2.50);
}
/**
* @return the type
*/
public Type getType() {
return type;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
/**
* @author Oliver Gierke
*/
@RepositoryRestResource(collectionResourceDescription = @Description("Collection resource description"),
itemResourceDescription = @Description("Item resource description."))
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByType(@Param("type") Type type);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2014-2016 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.jpa;
import java.math.BigDecimal;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.config.Projection;
/**
* @author Oliver Gierke
*/
@Projection(name = "summary", types = Order.class)
@Description("A summary of an order.")
public interface OrderSummary {
@Description("Price!!")
BigDecimal getPrice();
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2012-2016 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.jpa;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import org.springframework.data.rest.core.annotation.Description;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* An entity that represents a person.
*
* @author Jon Brisbin
*/
@Entity
@JsonIgnoreProperties({ "height", "weight" })
public class Person {
@Id @GeneratedValue private Long id;
@Description("A person's first name") //
private String firstName;
@Description("A person's last name") //
private String lastName;
@Description("A person's siblings") //
@ManyToMany //
private List<Person> siblings = new ArrayList<Person>();
@ManyToOne //
private Person father;
@Description("Timestamp this person object was created") //
private Date created;
@JsonIgnore //
private int age;
private int height, weight;
private Gender gender;
public Person() {}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
@NotNull
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Person addSibling(Person p) {
if (siblings == Collections.EMPTY_LIST) {
siblings = new ArrayList<Person>();
}
siblings.add(p);
return this;
}
public List<Person> getSiblings() {
return siblings;
}
public void setSiblings(List<Person> siblings) {
this.siblings = siblings;
}
public Person getFather() {
return father;
}
public void setFather(Person father) {
this.father = father;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {}
@PrePersist
private void prePersist() {
this.created = Calendar.getInstance().getTime();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public static enum Gender {
MALE, FEMALE, UNDEFINED;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
/**
* A repository to manage {@link Person}s.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
@RestResource(rel = "firstname", path = "firstname")
Page<Person> findByFirstName(@Param("firstname") String firstName, Pageable pageable);
@RestResource(rel = "lastname", path = "lastname")
List<Person> findByLastName(@Param("lastname") String lastName, Sort sort);
Person findFirstPersonByFirstName(@Param("firstname") String firstName);
Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
@Query("select p from Person p where p.created > :date")
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
Pageable pageable);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014-2016 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.jpa;
import org.springframework.data.rest.core.config.Projection;
/**
* @author Oliver Gierke
*/
@Projection(name = "excerpt", types = Person.class)
public interface PersonSummary {
String getFirstName();
String getLastName();
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2014-2016 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.jpa;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* An entity that represents a receipt.
*
* @author Pablo Lozano
*/
@Entity
@JsonIgnoreProperties({"version"})
public class Receipt {
@Id
@GeneratedValue
private Long id;
private String saleItem;
private BigDecimal amount;
@Version
@Temporal(TemporalType.TIMESTAMP)
private Date version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSaleItem() {
return saleItem;
}
public void setSaleItem(String saleItem) {
this.saleItem = saleItem;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Date getVersion() {
return version;
}
public void setVersion(Date version) {
this.version = version;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* A repository to manage {@link Receipt}s.
*
* @author Pablo Lozano
*/
public interface ReceiptRepository extends CrudRepository<Receipt, Long> {
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2014-2016 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.jpa;
/**
* @author Oliver Gierke
*/
public enum Type {
IN_STORE, TAKE_AWAY;
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2015-2016 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.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-463
*/
@Entity
public class User {
private @Id @GeneratedValue Long id;
private String name;
private @JsonIgnore String password;
private String[] roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String[] getRoles() {
return roles;
}
public void setRoles(String... roles) {
this.roles = roles;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2015-2016 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.jpa;
/**
* @author Oliver Gierke
* @soundtrack Elen - Sink like a stone (Elen)
*/
public interface UserExcerpt {
UserExcerpt getFather();
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2015-2016 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.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-463
*/
interface UserRepository extends CrudRepository<User, Long> {}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2014-2016 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.data.rest.tests.TestMvcClient.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for {@link RepositoryController}.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositoryControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositoryController controller;
/**
* @see DATAREST-333
*/
@Test
public void rootResourceExposesGetOnly() {
HttpEntity<?> response = controller.optionsForRepositories();
assertAllowHeaders(response, HttpMethod.GET);
}
/**
* @see DATAREST-333, DATAREST-330
*/
@Test
public void headRequestReturnsNoContent() {
assertThat(controller.headForRepositories().getStatusCode(), is(HttpStatus.NO_CONTENT));
}
/**
* @see DATAREST-160, DATAREST-333, DATAREST-463
*/
@Test
public void exposesLinksToRepositories() {
RepositoryLinksResource resource = controller.listRepositories().getBody();
assertThat(resource.getLinks(), hasSize(8));
assertThat(resource.hasLink("people"), is(true));
assertThat(resource.hasLink("orders"), is(true));
assertThat(resource.hasLink("addresses"), is(true));
assertThat(resource.hasLink("books"), is(true));
assertThat(resource.hasLink("authors"), is(true));
assertThat(resource.hasLink("receipts"), is(true));
assertThat(resource.hasLink("items"), is(true));
}
}

View File

@@ -0,0 +1,303 @@
/*
* Copyright 2014-2016 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.mockito.Mockito.*;
import static org.springframework.data.rest.tests.TestMvcClient.*;
import static org.springframework.http.HttpMethod.*;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.Address;
import org.springframework.data.rest.webmvc.jpa.AddressRepository;
import org.springframework.data.rest.webmvc.jpa.CreditCard;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.rest.webmvc.support.ETag;
import org.springframework.hateoas.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.HttpRequestMethodNotSupportedException;
/**
* Integration tests for {@link RepositoryEntityController}.
*
* @author Oliver Gierke
* @author Jeremy Rickard
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositoryEntityControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositoryEntityController controller;
@Autowired AddressRepository repository;
@Autowired RepositoryRestConfiguration configuration;
@Autowired PersistentEntityResourceAssembler assembler;
@Autowired PersistentEntities entities;
/**
* @see DATAREST-217
*/
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void returnsNotFoundForListingEntitiesIfFindAllNotExported() throws Exception {
repository.save(new Address());
RootResourceInformation request = getResourceInformation(Address.class);
controller.getCollectionResource(request, null, null, null);
}
/**
* @see DATAREST-217
*/
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void rejectsEntityCreationIfSaveIsNotExported() throws Exception {
RootResourceInformation request = getResourceInformation(Address.class);
controller.postCollectionResource(request, null, null, MediaType.APPLICATION_JSON_VALUE);
}
/**
* @see DATAREST-301
*/
@Test
public void setsExpandedSelfUriInLocationHeader() throws Exception {
RootResourceInformation information = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource
.build(new Order(new Person()), entities.getPersistentEntity(Order.class)).build();
ResponseEntity<?> entity = controller.putItemResource(information, persistentEntityResource, 1L, assembler,
ETag.NO_ETAG, MediaType.APPLICATION_JSON_VALUE);
assertThat(entity.getHeaders().getLocation().toString(), not(Matchers.endsWith("{?projection}")));
}
/**
* @see DATAREST-330
*/
@Test
public void exposesHeadForCollectionResourceIfExported() throws Exception {
ResponseEntity<?> entity = controller.headCollectionResource(getResourceInformation(Person.class),
new DefaultedPageable(null, false));
assertThat(entity.getStatusCode(), is(HttpStatus.NO_CONTENT));
}
/**
* @see DATAREST-330
*/
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeHeadForCollectionResourceIfNotExported() throws Exception {
controller.headCollectionResource(getResourceInformation(CreditCard.class), new DefaultedPageable(null, false));
}
/**
* @see DATAREST-330
*/
@Test
public void exposesHeadForItemResourceIfExported() throws Exception {
Address address = repository.save(new Address());
ResponseEntity<?> entity = controller.headForItemResource(getResourceInformation(Address.class), address.id,
assembler);
assertThat(entity.getStatusCode(), is(HttpStatus.NO_CONTENT));
}
/**
* @see DATAREST-330
*/
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeHeadForItemResourceIfNotExisting() throws Exception {
controller.headForItemResource(getResourceInformation(CreditCard.class), 1L, assembler);
}
/**
* @see DATAREST-333
*/
@Test
public void doesNotExposeMethodsForOptionsIfNotHttpMethodsSupportedForCollectionResource() {
HttpEntity<?> response = controller.optionsForCollectionResource(getResourceInformation(Address.class));
assertAllowHeaders(response, OPTIONS);
}
/**
* @see DATAREST-333
*/
@Test
public void exposesSupportedHttpMethodsInAllowHeaderForOptionsRequestToCollectionResource() {
HttpEntity<?> response = controller.optionsForCollectionResource(getResourceInformation(Person.class));
assertAllowHeaders(response, GET, POST, HEAD, OPTIONS);
}
/**
* @see DATAREST-333
*/
@Test
public void exposesSupportedHttpMethodsInAllowHeaderForOptionsRequestToItemResource() {
HttpEntity<?> response = controller.optionsForItemResource(getResourceInformation(Person.class));
assertAllowHeaders(response, GET, PUT, PATCH, DELETE, HEAD, OPTIONS);
}
/**
* @see DATAREST-333, DATAREST-348
*/
@Test
public void optionsForItermResourceSetsAllowPatchHeader() {
ResponseEntity<?> entity = controller.optionsForItemResource(getResourceInformation(Person.class));
List<String> value = entity.getHeaders().get("Accept-Patch");
assertThat(value, hasSize(3));
assertThat(value,
hasItems(//
RestMediaTypes.JSON_PATCH_JSON.toString(), //
RestMediaTypes.MERGE_PATCH_JSON.toString(), //
MediaType.APPLICATION_JSON_VALUE));
}
/**
* @see DATAREST-34
*/
@Test
public void returnsBodyOnPutForUpdateIfAcceptHeaderPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
Order order = request.getInvoker().invokeSave(new Order(new Person()));
PersistentEntityResource persistentEntityResource = PersistentEntityResource
.build(new Order(new Person()), entities.getPersistentEntity(Order.class)).build();
assertThat(controller.putItemResource(request, persistentEntityResource, order.getId(), assembler, ETag.NO_ETAG,
MediaType.APPLICATION_JSON_VALUE).hasBody(), is(true));
}
/**
* @see DATAREST-34
*/
@Test
public void returnsBodyForCreatingPutIfAcceptHeaderPresentByDefault() throws HttpRequestMethodNotSupportedException {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource
.build(new Order(new Person()), entities.getPersistentEntity(Order.class)).build();
assertThat(controller.putItemResource(request, persistentEntityResource, 1L, assembler, ETag.NO_ETAG,
MediaType.APPLICATION_JSON_VALUE).hasBody(), is(true));
}
/**
* @see DATAREST-34
*/
@Test
public void returnsBodyForPostIfAcceptHeaderIsPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource
.build(new Order(new Person()), entities.getPersistentEntity(Order.class)).build();
assertThat(controller
.postCollectionResource(request, persistentEntityResource, assembler, MediaType.APPLICATION_JSON_VALUE)
.hasBody(), is(true));
}
/**
* @see DATAREST-34
*/
@Test
public void doesNotReturnBodyForPostIfNoAcceptHeaderPresentByDefault() throws Exception {
RootResourceInformation request = getResourceInformation(Order.class);
PersistentEntityResource persistentEntityResource = PersistentEntityResource
.build(new Order(new Person()), entities.getPersistentEntity(Order.class)).build();
assertThat(controller.postCollectionResource(request, persistentEntityResource, assembler, null).hasBody(),
is(false));
assertThat(controller.postCollectionResource(request, persistentEntityResource, assembler, "").hasBody(),
is(false));
}
/**
* @see DATAREST-581
*/
@Test
public void createsEtagForProjectedEntityCorrectly() throws Exception {
Address address = repository.save(new Address());
PersistentEntityResourceAssembler assembler = Mockito.mock(PersistentEntityResourceAssembler.class);
AddressProjection addressProjection = new SpelAwareProxyProjectionFactory()
.createProjection(AddressProjection.class);
PersistentEntityResource resource = PersistentEntityResource
.build(addressProjection, entities.getPersistentEntity(Address.class)).build();
Mockito.when(assembler.toFullResource(Mockito.any(Object.class))).thenReturn(resource);
ResponseEntity<Resource<?>> entity = controller.getItemResource(getResourceInformation(Address.class), address.id,
assembler, new LinkedMultiValueMap<String, String>());
assertThat(entity.getHeaders().getETag(), is(notNullValue()));
}
/**
* @see DATAREST-724
*/
@Test
public void deletesEntityWithCustomLookupCorrectly() throws Exception {
Address address = repository.save(new Address());
assertThat(repository.findOne(address.id), is(notNullValue()));
RootResourceInformation resourceInformation = getResourceInformation(Address.class);
RepositoryInvoker invoker = spy(resourceInformation.getInvoker());
doReturn(address).when(invoker).invokeFindOne("foo");
RootResourceInformation informationSpy = Mockito.spy(resourceInformation);
doReturn(invoker).when(informationSpy).getInvoker();
controller.deleteItemResource(informationSpy, "foo", ETag.from("0"));
assertThat(repository.findOne(address.id), is(nullValue()));
}
interface AddressProjection {}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2015-2016 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.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.Book;
import org.springframework.data.rest.webmvc.jpa.BookRepository;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.TestDataPopulator;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositoryPropertyReferenceControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositoryPropertyReferenceController controller;
@Autowired TestDataPopulator populator;
@Autowired BookRepository books;
PersistentEntityResourceAssembler assembler;
RootResourceInformation information;
@Before
public void setUp() {
this.assembler = mock(PersistentEntityResourceAssembler.class);
this.information = getResourceInformation(Book.class);
this.populator.populateRepositories();
}
@Test
public void exposesResourceForCustomizedPropertyResourcePath() throws Exception {
Book book = books.findAll().iterator().next();
assertThat(controller.followPropertyReference(information, book.id, "creators", assembler).getStatusCode(),
is(HttpStatus.OK));
}
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeOriginalPathIfPropertyResourcePathIsCustomized() throws Exception {
Book book = books.findAll().iterator().next();
controller.followPropertyReference(information, book.id, "authors", assembler);
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2013-2016 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.data.rest.tests.TestMvcClient.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.tests.ResourceTester;
import org.springframework.data.rest.tests.ResourceTester.HasSelfLink;
import org.springframework.data.rest.webmvc.jpa.Address;
import org.springframework.data.rest.webmvc.jpa.Author;
import org.springframework.data.rest.webmvc.jpa.Book;
import org.springframework.data.rest.webmvc.jpa.CreditCard;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.TestDataPopulator;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Integration tests for the {@link RepositorySearchController}.
*
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositorySearchControllerIntegrationTests extends AbstractControllerIntegrationTests {
static final DefaultedPageable PAGEABLE = new DefaultedPageable(new PageRequest(0, 10), true);
@Autowired TestDataPopulator loader;
@Autowired RepositorySearchController controller;
@Autowired PersistentEntityResourceAssembler assembler;
@Before
public void setUp() {
loader.populateRepositories();
}
@Test
public void rendersCorrectSearchLinksForPersons() throws Exception {
RootResourceInformation request = getResourceInformation(Person.class);
ResourceSupport resource = controller.listSearches(request);
ResourceTester tester = ResourceTester.of(resource);
tester.assertNumberOfLinks(6); // Self link included
tester.assertHasLinkEndingWith("findFirstPersonByFirstName", "findFirstPersonByFirstName{?firstname,projection}");
tester.assertHasLinkEndingWith("firstname", "firstname{?firstname,page,size,sort,projection}");
tester.assertHasLinkEndingWith("lastname", "lastname{?lastname,sort,projection}");
tester.assertHasLinkEndingWith("findByCreatedUsingISO8601Date",
"findByCreatedUsingISO8601Date{?date,page,size,sort,projection}");
tester.assertHasLinkEndingWith("findByCreatedGreaterThan",
"findByCreatedGreaterThan{?date,page,size,sort,projection}");
}
@Test(expected = ResourceNotFoundException.class)
public void returns404ForUnexportedRepository() {
controller.listSearches(getResourceInformation(CreditCard.class));
}
@Test(expected = ResourceNotFoundException.class)
public void returns404ForRepositoryWithoutSearches() {
controller.listSearches(getResourceInformation(Author.class));
}
@Test
public void executesSearchAgainstRepository() {
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
parameters.add("firstname", "John");
ResponseEntity<Object> response = controller.executeSearch(resourceInformation, parameters, "firstname", PAGEABLE,
null, assembler);
ResourceTester tester = ResourceTester.of(response.getBody());
PagedResources<Object> pagedResources = tester.assertIsPage();
assertThat(pagedResources.getContent().size(), is(1));
ResourceMetadata metadata = getMetadata(Person.class);
tester.withContentResource(new HasSelfLink(BASE.slash(metadata.getPath()).slash("{id}")));
}
/**
* @see DATAREST-330
*/
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeHeadForSearchResourceIfResourceDoesnHaveSearches() {
controller.headForSearches(getResourceInformation(Author.class));
}
/**
* @see DATAREST-330
*/
@Test(expected = ResourceNotFoundException.class)
public void exposesHeadForSearchResourceIfResourceIsNotExposed() {
controller.headForSearches(getResourceInformation(CreditCard.class));
}
/**
* @see DATAREST-330
*/
@Test
public void exposesHeadForSearchResourceIfResourceIsExposed() {
controller.headForSearches(getResourceInformation(Person.class));
}
/**
* @see DATAREST-330
*/
@Test
public void exposesHeadForExistingQueryMethodResource() {
controller.headForSearch(getResourceInformation(Person.class), "findByCreatedUsingISO8601Date");
}
/**
* @see DATAREST-330
*/
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeHeadForInvalidQueryMethodResource() {
controller.headForSearch(getResourceInformation(Person.class), "foobar");
}
/**
* @see DATAREST-333
*/
@Test
public void searchResourceSupportsGetOnly() {
assertAllowHeaders(controller.optionsForSearches(getResourceInformation(Person.class)), HttpMethod.GET);
}
/**
* @see DATAREST-333
*/
@Test(expected = ResourceNotFoundException.class)
public void returns404ForOptionsForRepositoryWithoutSearches() {
controller.optionsForSearches(getResourceInformation(Address.class));
}
/**
* @see DATAREST-333
*/
@Test
public void queryMethodResourceSupportsGetOnly() {
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
HttpEntity<Object> response = controller.optionsForSearch(resourceInformation, "firstname");
assertAllowHeaders(response, HttpMethod.GET);
}
/**
* @see DATAREST-502
*/
@Test
public void interpretsUriAsReferenceToRelatedEntity() {
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
parameters.add("author", "/author/1");
RootResourceInformation resourceInformation = getResourceInformation(Book.class);
ResponseEntity<Object> result = controller.executeSearch(resourceInformation, parameters, "findByAuthorsContains",
PAGEABLE, null, assembler);
assertThat(result.getBody(), is(instanceOf(Resources.class)));
}
/**
* @see DATAREST-515
*/
@Test
public void repositorySearchResourceExposesDomainType() {
RepositorySearchesResource searches = controller.listSearches(getResourceInformation(Person.class));
assertThat(searches.getDomainType(), is(typeCompatibleWith(Person.class)));
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2014-2016 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.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.rest.core.mapping.ResourceType.*;
import static org.springframework.http.HttpMethod.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.Address;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for {@link RootResourceInformation}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RootResourceInformationIntegrationTests extends AbstractControllerIntegrationTests {
/**
* @see DATAREST-217
*/
@Test
public void getIsNotSupportedIfFindAllIsNotExported() {
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
assertThat(supportedMethods.getMethodsFor(COLLECTION), not(hasItem(GET)));
}
/**
* @see DATAREST-217
*/
@Test
public void postIsNotSupportedIfSaveIsNotExported() {
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
assertThat(supportedMethods.getMethodsFor(COLLECTION), not(hasItem(POST)));
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2014-2016 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.alps;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.After;
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.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.tests.TestMvcClient;
import org.springframework.data.rest.webmvc.ProfileController;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.data.rest.webmvc.alps.AlpsController;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.data.rest.webmvc.jpa.Item;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.hateoas.core.JsonPathLinkDiscoverer;
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;
/**
* Integration tests for {@link AlpsController}.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@WebAppConfiguration
@ContextConfiguration(classes = { JpaRepositoryConfig.class, AlpsControllerIntegrationTests.Config.class })
public class AlpsControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired WebApplicationContext context;
@Autowired LinkDiscoverers discoverers;
@Autowired RepositoryRestConfiguration configuration;
@Configuration
static class Config extends RepositoryRestConfigurerAdapter {
@Bean
public LinkDiscoverer alpsLinkDiscoverer() {
return new JsonPathLinkDiscoverer("$.descriptors[?(@.name == '%s')].href",
MediaType.valueOf("application/alps+json"));
}
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Item.class);
}
}
TestMvcClient client;
@Before
public void setUp() {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
this.client = new TestMvcClient(mvc, this.discoverers);
}
@After
public void tearDown() {
configuration.setEnableEnumTranslation(false);
}
/**
* @see DATAREST-230
*/
@Test
public void exposesAlpsCollectionResources() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link peopleLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
client.follow(peopleLink, RestMediaTypes.ALPS_JSON)//
.andExpect(jsonPath("$.alps.version").value("1.0"))//
.andExpect(jsonPath("$.alps.descriptors[*].name", hasItems("people", "person")));
}
/**
* @see DATAREST-638
*/
@Test
public void verifyThatAlpsIsDefaultProfileFormat() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link peopleLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
client.follow(peopleLink)//
.andExpect(jsonPath("$.alps.version").value("1.0"))//
.andExpect(jsonPath("$.alps.descriptors[*].name", hasItems("people", "person")));
}
/**
* @see DATAREST-463
*/
@Test
public void verifyThatAttributesIgnoredDontAppearInAlps() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link itemsLink = client.discoverUnique(profileLink, "items", MediaType.ALL);
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
// Exposes standard property
.andExpect(jsonPath("$.alps.descriptors[*].descriptors[*].name", hasItems("name")))
// Does not expose explicitly @JsonIgnored property
.andExpect(jsonPath("$.alps.descriptors[*].descriptors[*].name", not(hasItems("owner"))))
// Does not expose properties pointing to non exposed types
.andExpect(jsonPath("$.alps.descriptors[*].descriptors[*].name", not(hasItems("manager", "curator"))));
}
/**
* @see DATAREST-494
*/
@Test
public void linksToJsonSchemaFromRepresentationDescriptor() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link itemsLink = client.discoverUnique(profileLink, "items", MediaType.ALL);
assertThat(itemsLink, is(notNullValue()));
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
.andExpect(
jsonPath("$.alps.descriptors[?(@.id == 'item-representation')][0].href", endsWith("/profile/items")));
}
/**
* @see DATAREST-516
*/
@Test
public void referenceToAssociatedEntityDesciptorPointsToRepresentationDescriptor() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link usersLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
String jsonPath = "$.alps."; // Root
jsonPath += "descriptors[?(@.id == 'person-representation')]."; // Representation descriptor
jsonPath += "descriptors[?(@.name == 'father')][0]."; // First father descriptor
jsonPath += "rt"; // Return type
client.follow(usersLink, RestMediaTypes.ALPS_JSON)//
.andExpect(jsonPath(jsonPath,
allOf(containsString(ProfileController.PROFILE_ROOT_MAPPING), endsWith("-representation"))));
}
/**
* @see DATAREST-630
*/
@Test
public void onlyExposesIdAttributesWhenExposedInTheConfiguration() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link itemsLink = client.discoverUnique(profileLink, "items", MediaType.ALL);
client.follow(itemsLink, RestMediaTypes.ALPS_JSON)//
// Exposes identifier if configured to
.andExpect(jsonPath("$.alps.descriptors[*].descriptors[*].name", hasItems("id", "name")));
}
/**
* @see DATAREST-683
*/
@Test
public void enumValueListingsAreTranslatedIfEnabled() throws Exception {
configuration.setEnableEnumTranslation(true);
Link profileLink = client.discoverUnique("profile");
Link peopleLink = client.discoverUnique(profileLink, "people", MediaType.ALL);
client.follow(peopleLink)//
.andExpect(jsonPath(
"$.alps.descriptors[?(@.id == 'person-representation')].descriptors[?(@.name == 'gender')][0].doc.value",
is("Male, Female, Undefined")));
}
/**
* @see DATAREST-753
*/
@Test
public void alpsCanHandleGroovyDomainObjects() throws Exception {
Link profileLink = client.discoverUnique("profile");
Link groovyDomainObjectLink = client.discoverUnique(profileLink, "simulatedGroovyDomainClasses");
client.follow(groovyDomainObjectLink)//
.andExpect(jsonPath(
"$.alps.descriptors[?(@.id == 'simulatedGroovyDomainClass-representation')][0].descriptors[0].name",
is("name")));
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2014-2016 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.jpa;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.mapping.JpaPersistentEntity;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for DATAREST-262, checking serialization and deserialization of associations within embeddables.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DataRest262Tests {
@Configuration
@Import({ RepositoryRestMvcConfiguration.class, JpaInfrastructureConfig.class })
@EnableJpaRepositories(considerNestedRepositories = true)
static class Config {
}
@Autowired ApplicationContext beanFactory;
@Autowired JpaMetamodelMappingContext mappingContext;
@Autowired AirportRepository repository;
@Autowired @Qualifier("halObjectMapper") ObjectMapper mapper;
@Before
public void setUp() {
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
/**
* @see DATAREST-262
*/
@Test
public void deserializesNestedAssociation() throws Exception {
Airport airport = repository.save(new Airport());
String payload = "{\"orgOrDstFlightPart\":{\"airport\":\"/api/airports/" + airport.id + "\"}}";
AircraftMovement result = mapper.readValue(payload, AircraftMovement.class);
assertThat(result.orgOrDstFlightPart.airport.id, is(airport.id));
}
/**
* @see DATAREST-262
*/
@Test
@Ignore
public void serializesLinksToNestedAssociations() throws Exception {
Airport first = new Airport();
first.id = 1L;
Airport second = new Airport();
second.id = 2L;
FlightPart part = new FlightPart();
part.airport = second;
AircraftMovement movement = new AircraftMovement();
movement.id = 3L;
movement.originOrDestinationAirport = first;
movement.orgOrDstFlightPart = part;
JpaPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(AircraftMovement.class);
Resource<Object> resource = PersistentEntityResource.build(movement, persistentEntity).//
withLink(new Link("/api/airports/" + movement.id)).//
build();
String result = mapper.writeValueAsString(resource);
assertThat(JsonPath.read(result, "$_links.self"), is(notNullValue()));
assertThat(JsonPath.read(result, "$_links.airport"), is(notNullValue()));
assertThat(JsonPath.read(result, "$_links.originOrDestinationAirport"), is(notNullValue()));
}
public interface AircraftMovementRepository extends CrudRepository<AircraftMovement, Long> {
}
public interface AirportRepository extends CrudRepository<Airport, Long> {
}
@Entity(name = "aircraftmovement")
public static class AircraftMovement {
@Id @GeneratedValue Long id;
@ManyToOne Airport originOrDestinationAirport;
@Embedded @NotNull FlightPart orgOrDstFlightPart;
}
@Embeddable
public static class FlightPart {
@ManyToOne Airport airport;
}
@Entity(name = "airport")
public static class Airport {
@Id @GeneratedValue Long id;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2015-2016 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.jpa;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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.tests.TestMvcClient;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.hateoas.core.JsonPathLinkDiscoverer;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* Integration tests for DATAREST-363.
*
* @author Greg Turnquist
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(
classes = { JpaRepositoryConfig.class, RepositoryRestMvcConfiguration.class, DataRest363Tests.Config.class })
public class DataRest363Tests {
private static MediaType MEDIA_TYPE = MediaType.APPLICATION_JSON;
@Autowired WebApplicationContext context;
@Autowired LinkDiscoverers discoverers;
@Autowired PersonRepository personRepository;
TestMvcClient testMvcClient;
Person frodo;
@Configuration
static class Config extends RepositoryRestConfigurerAdapter {
@Bean
public LinkDiscoverer classicLinkDiscover() {
return new JsonPathLinkDiscoverer("$.links[?(@.rel == '%s')].href", MEDIA_TYPE);
}
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultMediaType(MEDIA_TYPE).useHalAsDefaultJsonMediaType(false);
}
}
@Before
public void setUp() {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).//
defaultRequest(get("/")).build();
this.testMvcClient = new TestMvcClient(mvc, discoverers);
this.frodo = personRepository.save(new Person("Frodo", "Baggins"));
}
/**
* @see DATAREST-363
*/
@Test
public void testBasics() throws Exception {
ResultActions frodoActions = testMvcClient.follow("/people/".concat(frodo.getId().toString()));
frodoActions.andExpect(jsonPath("$.links").value(hasSize(4)));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2014-2016 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.jpa;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author Oliver Gierke
*/
@Configuration
public class JpaInfrastructureConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setPersistenceUnitName("spring-data-rest-webmvc");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2016 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.jpa;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Test configuration for JPA.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
public class JpaRepositoryConfig extends JpaInfrastructureConfig {
@Bean
public BookIdConverter bookIdConverter() {
return new BookIdConverter();
}
@Bean
public TestDataPopulator testDataPopulator() {
return new TestDataPopulator();
}
@BasePathAwareController
static class BooksHtmlController {
@RequestMapping(value = "/books/{id}", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
void person(@PathVariable String id) {}
}
}

View File

@@ -0,0 +1,721 @@
/*
* Copyright 2013-2016 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.jpa;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.rest.webmvc.util.TestUtils.*;
import static org.springframework.http.HttpHeaders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import net.minidev.json.JSONArray;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.tests.CommonWebTests;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.RelProvider;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Web integration tests specific to JPA.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@Transactional
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class JpaWebTests extends CommonWebTests {
private static final MediaType TEXT_URI_LIST = MediaType.valueOf("text/uri-list");
static final String LINK_TO_SIBLINGS_OF = "$._embedded..[?(@.firstName == '%s')]._links.siblings.href[0]";
@Autowired TestDataPopulator loader;
@Autowired ResourceMappings mappings;
@Autowired RelProvider relProvider;
ObjectMapper mapper = new ObjectMapper();
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#setUp()
*/
@Override
@Before
public void setUp() {
loader.populateRepositories();
super.setUp();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#expectedRootLinkRels()
*/
@Override
protected Iterable<String> expectedRootLinkRels() {
return Arrays.asList("people", "authors", "books");
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#getPayloadToPost()
*/
@Override
protected Map<String, String> getPayloadToPost() throws Exception {
return Collections.singletonMap("people", readFileFromClasspath("person.json"));
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#getRootAndLinkedResources()
*/
@Override
protected MultiValueMap<String, String> getRootAndLinkedResources() {
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("authors", "books");
map.add("books", "authors");
return map;
}
/**
* @see DATAREST-99
*/
@Test
public void doesNotExposeCreditCardRepository() throws Exception {
mvc.perform(get("/")). //
andExpect(status().isOk()). //
andExpect(doesNotHaveLinkWithRel(mappings.getMetadataFor(CreditCard.class).getRel()));
}
@Test
public void accessPersons() throws Exception {
MockHttpServletResponse response = client.request("/people?page=0&size=1");
Link nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response);
assertDoesNotHaveLinkWithRel(Link.REL_PREVIOUS, response);
response = client.request(nextLink);
client.assertHasLinkWithRel(Link.REL_PREVIOUS, response);
nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response);
response = client.request(nextLink);
client.assertHasLinkWithRel(Link.REL_PREVIOUS, response);
assertDoesNotHaveLinkWithRel(Link.REL_NEXT, response);
}
/**
* @see DATAREST-169
*/
@Test
public void exposesLinkForRelatedResource() throws Exception {
MockHttpServletResponse response = client.request("/");
Link ordersLink = client.assertHasLinkWithRel("orders", response);
MockHttpServletResponse orders = client.request(ordersLink);
Link creatorLink = assertHasContentLinkWithRel("creator", orders);
assertThat(client.request(creatorLink), is(notNullValue()));
}
/**
* @see DATAREST-200
*/
@Test
public void exposesInlinedEntities() throws Exception {
MockHttpServletResponse response = client.request("/");
Link ordersLink = client.assertHasLinkWithRel("orders", response);
MockHttpServletResponse orders = client.request(ordersLink);
assertHasJsonPathValue("$..lineItems", orders);
}
/**
* @see DATAREST-199
*/
@Test
public void createsOrderUsingPut() throws Exception {
mvc.perform(//
put("/orders/{id}", 4711).//
content(readFileFromClasspath("order.json")).contentType(MediaType.APPLICATION_JSON)//
).andExpect(status().isCreated());
}
/**
* @see DATAREST-117
*/
@Test
public void createPersonThenVerifyIgnoredAttributesDontExist() throws Exception {
Link peopleLink = client.discoverUnique("people");
ObjectMapper mapper = new ObjectMapper();
Person frodo = new Person("Frodo", "Baggins");
frodo.setAge(77);
frodo.setHeight(42);
frodo.setWeight(75);
String frodoString = mapper.writeValueAsString(frodo);
MockHttpServletResponse response = postAndGet(peopleLink, frodoString, MediaType.APPLICATION_JSON);
assertJsonPathEquals("$.firstName", "Frodo", response);
assertJsonPathEquals("$.lastName", "Baggins", response);
assertJsonPathDoesntExist("$.age", response);
assertJsonPathDoesntExist("$.height", response);
assertJsonPathDoesntExist("$.weight", response);
}
/**
* @see DATAREST-95
*/
@Test
public void createThenPatch() throws Exception {
Link peopleLink = client.discoverUnique("people");
MockHttpServletResponse bilbo = postAndGet(peopleLink, "{ \"firstName\" : \"Bilbo\", \"lastName\" : \"Baggins\" }",
MediaType.APPLICATION_JSON);
Link bilboLink = client.assertHasLinkWithRel("self", bilbo);
assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.firstName"), is("Bilbo"));
assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.lastName"), is("Baggins"));
MockHttpServletResponse frodo = patchAndGet(bilboLink, "{ \"firstName\" : \"Frodo\" }", MediaType.APPLICATION_JSON);
assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), is("Frodo"));
assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.lastName"), is("Baggins"));
frodo = patchAndGet(bilboLink, "{ \"firstName\" : null }", MediaType.APPLICATION_JSON);
assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), is(nullValue()));
assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.lastName"), is("Baggins"));
}
/**
* @see DATAREST-150
*/
@Test
public void createThenPut() throws Exception {
Link peopleLink = client.discoverUnique("people");
MockHttpServletResponse bilbo = postAndGet(peopleLink, //
"{ \"firstName\" : \"Bilbo\", \"lastName\" : \"Baggins\" }", //
MediaType.APPLICATION_JSON);
Link bilboLink = client.assertHasLinkWithRel("self", bilbo);
assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.firstName"), equalTo("Bilbo"));
assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.lastName"), equalTo("Baggins"));
MockHttpServletResponse frodo = putAndGet(bilboLink, //
"{ \"firstName\" : \"Frodo\" }", //
MediaType.APPLICATION_JSON);
assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), equalTo("Frodo"));
assertNull(JsonPath.read(frodo.getContentAsString(), "$.lastName"));
}
@Test
public void listsSiblingsWithContentCorrectly() throws Exception {
assertPersonWithNameAndSiblingLink("John");
}
@Test
public void listsEmptySiblingsCorrectly() throws Exception {
assertPersonWithNameAndSiblingLink("Billy Bob");
}
/**
* @see DATAREST-219
*/
@Test
public void manipulatePropertyCollectionRestfullyWithMultiplePosts() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"), //
new Person("Merry", "Baggins"), //
new Person("Pippin", "Baggins"));
Link frodosSiblingLink = links.get(0);
patchAndGet(frodosSiblingLink, links.get(1).getHref(), TEXT_URI_LIST);
patchAndGet(frodosSiblingLink, links.get(2).getHref(), TEXT_URI_LIST);
patchAndGet(frodosSiblingLink, links.get(3).getHref(), TEXT_URI_LIST);
assertSiblingNames(frodosSiblingLink, "Bilbo", "Merry", "Pippin");
}
/**
* @see DATAREST-219
*/
@Test
public void manipulatePropertyCollectionRestfullyWithSinglePost() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"), //
new Person("Merry", "Baggins"), //
new Person("Pippin", "Baggins"));
Link frodosSiblingLink = links.get(0);
patchAndGet(frodosSiblingLink, toUriList(links.get(1), links.get(2), links.get(3)), TEXT_URI_LIST);
assertSiblingNames(frodosSiblingLink, "Bilbo", "Merry", "Pippin");
}
/**
* @see DATAREST-219
*/
@Test
public void manipulatePropertyCollectionRestfullyWithMultiplePuts() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"), //
new Person("Merry", "Baggins"), //
new Person("Pippin", "Baggins"));
Link frodosSiblingsLink = links.get(0);
putAndGet(frodosSiblingsLink, links.get(1).expand().getHref(), TEXT_URI_LIST);
putAndGet(frodosSiblingsLink, links.get(2).expand().getHref(), TEXT_URI_LIST);
putAndGet(frodosSiblingsLink, links.get(3).expand().getHref(), TEXT_URI_LIST);
assertSiblingNames(frodosSiblingsLink, "Pippin");
patchAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
assertSiblingNames(frodosSiblingsLink, "Merry", "Pippin");
}
/**
* @see DATAREST-219
*/
@Test
public void manipulatePropertyCollectionRestfullyWithSinglePut() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"), //
new Person("Merry", "Baggins"), //
new Person("Pippin", "Baggins"));
Link frodoSiblingLink = links.get(0);
putAndGet(frodoSiblingLink, toUriList(links.get(1), links.get(2), links.get(3)), TEXT_URI_LIST);
assertSiblingNames(frodoSiblingLink, "Bilbo", "Merry", "Pippin");
putAndGet(frodoSiblingLink, toUriList(links.get(3)), TEXT_URI_LIST);
assertSiblingNames(frodoSiblingLink, "Pippin");
patchAndGet(frodoSiblingLink, toUriList(links.get(2)), TEXT_URI_LIST);
assertSiblingNames(frodoSiblingLink, "Merry", "Pippin");
}
/**
* @see DATAREST-219
*/
@Test
public void manipulatePropertyCollectionRestfullyWithDelete() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"), //
new Person("Merry", "Baggins"), //
new Person("Pippin", "Baggins"));
Link frodosSiblingsLink = links.get(0);
patchAndGet(frodosSiblingsLink, links.get(1).getHref(), TEXT_URI_LIST);
patchAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
patchAndGet(frodosSiblingsLink, links.get(3).getHref(), TEXT_URI_LIST);
String pippinId = new UriTemplate("/people/{id}").match(links.get(3).getHref()).get("id");
deleteAndVerify(new Link(frodosSiblingsLink.getHref() + "/" + pippinId));
assertSiblingNames(frodosSiblingsLink, "Bilbo", "Merry");
}
/**
* @see DATAREST-50
*/
@Test
public void propertiesCanHaveNulls() throws Exception {
Link peopleLink = client.discoverUnique("people");
Person frodo = new Person();
frodo.setFirstName("Frodo");
frodo.setLastName(null);
MockHttpServletResponse response = postAndGet(peopleLink, mapper.writeValueAsString(frodo),
MediaType.APPLICATION_JSON);
String responseBody = response.getContentAsString();
assertEquals(JsonPath.read(responseBody, "$.firstName"), "Frodo");
assertNull(JsonPath.read(responseBody, "$.lastName"));
}
/**
* @see DATAREST-238
*/
@Test
public void putShouldWorkDespiteExistingLinks() throws Exception {
Link peopleLink = client.discoverUnique("people");
Person frodo = new Person("Frodo", "Baggins");
String frodoString = mapper.writeValueAsString(frodo);
MockHttpServletResponse createdPerson = postAndGet(peopleLink, frodoString, MediaType.APPLICATION_JSON);
Link frodoLink = client.assertHasLinkWithRel("self", createdPerson);
assertJsonPathEquals("$.firstName", "Frodo", createdPerson);
String bilboWithFrodosLinks = createdPerson.getContentAsString().replace("Frodo", "Bilbo");
MockHttpServletResponse overwrittenResponse = putAndGet(frodoLink, bilboWithFrodosLinks,
MediaType.APPLICATION_JSON);
client.assertHasLinkWithRel("self", overwrittenResponse);
assertJsonPathEquals("$.firstName", "Bilbo", overwrittenResponse);
}
/**
* @see DATAREST-217
*/
@Test
public void doesNotAllowGetToCollectionResourceIfFindAllIsNotExported() throws Exception {
Link link = client.discoverUnique("addresses");
mvc.perform(get(link.getHref())).//
andExpect(status().isMethodNotAllowed());
}
/**
* @see DATAREST-217
*/
@Test
public void doesNotAllowPostToCollectionResourceIfSaveIsNotExported() throws Exception {
Link link = client.discoverUnique("addresses");
mvc.perform(post(link.getHref()).content("{}").contentType(MediaType.APPLICATION_JSON)).//
andExpect(status().isMethodNotAllowed());
}
/**
* Checks, that the server only returns the properties contained in the projection requested.
*
* @see OrderSummary
* @see DATAREST-221
*/
@Test
public void returnsProjectionIfRequested() throws Exception {
Link orders = client.discoverUnique("orders");
MockHttpServletResponse response = client.request(orders);
Link orderLink = assertContentLinkWithRel("self", response, true).expand();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(orderLink.getHref());
String uri = builder.queryParam("projection", "summary").build().toUriString();
response = mvc.perform(get(uri)). //
andExpect(status().isOk()). //
andExpect(jsonPath("$.price", is(2.5))).//
andReturn().getResponse();
assertJsonPathDoesntExist("$.lineItems", response);
}
/**
* @see DATAREST-261
*/
@Test
public void relProviderDetectsCustomizedMapping() {
assertThat(relProvider.getCollectionResourceRelFor(Person.class), is("people"));
}
/**
* @see DATAREST-311
*/
@Test
public void onlyLinksShouldAppearWhenExecuteSearchCompact() throws Exception {
Link peopleLink = client.discoverUnique("people");
Person daenerys = new Person("Daenerys", "Targaryen");
String daenerysString = mapper.writeValueAsString(daenerys);
MockHttpServletResponse createdPerson = postAndGet(peopleLink, daenerysString, MediaType.APPLICATION_JSON);
Link daenerysLink = client.assertHasLinkWithRel("self", createdPerson);
assertJsonPathEquals("$.firstName", "Daenerys", createdPerson);
Link searchLink = client.discoverUnique(peopleLink, "search");
Link byFirstNameLink = client.discoverUnique(searchLink, "findFirstPersonByFirstName");
MockHttpServletResponse response = client.request(byFirstNameLink.expand("Daenerys"),
MediaType.parseMediaType("application/x-spring-data-compact+json"));
String responseBody = response.getContentAsString();
JSONArray personLinks = JsonPath.<JSONArray> read(responseBody, "$.links[?(@.rel=='person')].href");
assertThat(personLinks, hasSize(1));
assertThat(personLinks.get(0), is((Object) daenerysLink.getHref()));
assertThat(JsonPath.<JSONArray> read(responseBody, "$.content"), hasSize(0));
}
/**
* @see DATAREST-317
*/
@Test
public void rendersExcerptProjectionsCorrectly() throws Exception {
Link authorsLink = client.discoverUnique("authors");
MockHttpServletResponse response = client.request(authorsLink);
String firstAuthorPath = "$._embedded.authors[0]";
// Has main content
assertHasJsonPathValue(firstAuthorPath.concat(".name"), response);
// Embeddes content of related entity, self link and keeps relation link
assertHasJsonPathValue(firstAuthorPath.concat("._embedded.books[0].title"), response);
assertHasJsonPathValue(firstAuthorPath.concat("._embedded.books[0]._links.self"), response);
assertHasJsonPathValue(firstAuthorPath.concat("._links.books"), response);
// Access item resource and expect link to related resource present
String content = response.getContentAsString();
String href = JsonPath.read(content, firstAuthorPath.concat("._links.self.href"));
client.follow(new Link(href)).andExpect(client.hasLinkWithRel("books"));
}
/**
* @see DATAREST-353
*/
@Test
public void returns404WhenTryingToDeleteANonExistingResource() throws Exception {
Link receiptsLink = client.discoverUnique("receipts");
mvc.perform(delete(receiptsLink.getHref().concat("/{id}"), 4711)).//
andExpect(status().isNotFound());
}
/**
* @see DATAREST-384
*/
@Test
public void execturesSearchThatTakesASort() throws Exception {
Link booksLink = client.discoverUnique("books");
Link searchLink = client.discoverUnique(booksLink, "search");
Link findBySortedLink = client.discoverUnique(searchLink, "find-by-sorted");
// Assert sort options advertised
assertThat(findBySortedLink.isTemplated(), is(true));
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
// Assert results returned as specified
client.follow(findBySortedLink.expand("title,desc")).//
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
andExpect(client.hasLinkWithRel("self"));
client.follow(findBySortedLink.expand("title,asc")).//
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
andExpect(client.hasLinkWithRel("self"));
}
/**
* @see DATAREST-160
*/
@Test
public void returnConflictWhenConcurrentlyEditingVersionedEntity() throws Exception {
Link receiptLink = client.discoverUnique("receipts");
Receipt receipt = new Receipt();
receipt.setAmount(new BigDecimal(50));
receipt.setSaleItem("Springy Tacos");
String stringReceipt = mapper.writeValueAsString(receipt);
MockHttpServletResponse createdReceipt = postAndGet(receiptLink, stringReceipt, MediaType.APPLICATION_JSON);
Link tacosLink = client.assertHasLinkWithRel("self", createdReceipt);
assertJsonPathEquals("$.saleItem", "Springy Tacos", createdReceipt);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(tacosLink.getHref());
String concurrencyTag = createdReceipt.getHeader("ETag");
mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyBurritos\" }")
.contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, concurrencyTag))
.andExpect(status().is2xxSuccessful());
mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyTequila\" }")
.contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, "\"falseETag\""))
.andExpect(status().isPreconditionFailed());
}
/**
* @see DATAREST-423
*/
@Test
public void invokesCustomControllerAndBindsDomainObjectCorrectly() throws Exception {
MockHttpServletResponse authorsResponse = client.request(client.discoverUnique("authors"));
String authorUri = JsonPath.read(authorsResponse.getContentAsString(), "$._embedded.authors[0]._links.self.href");
mvc.perform(delete(authorUri)).//
andExpect(status().isIAmATeapot());
}
/**
* @see DATAREST-523
*/
@Test
public void augmentsCollectionAssociationUsingPost() throws Exception {
List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
new Person("Bilbo", "Baggins"));
Link frodosSiblingsLink = links.get(0).expand();
Link bilboLink = links.get(1);
for (int i = 1; i <= 2; i++) {
mvc.perform(post(frodosSiblingsLink.getHref()).//
content(bilboLink.getHref()).//
contentType(TEXT_URI_LIST)).//
andExpect(status().isNoContent());
mvc.perform(get(frodosSiblingsLink.getHref())).//
andExpect(jsonPath("$._embedded.people", hasSize(i)));
}
}
/**
* @see DATAREST-658
*/
@Test
public void returnsLinkHeadersForHeadRequestToItemResource() throws Exception {
MockHttpServletResponse response = client.request(client.discoverUnique("people"));
String personHref = JsonPath.read(response.getContentAsString(), "$._embedded.people[0]._links.self.href");
response = mvc.perform(head(personHref))//
.andExpect(status().isNoContent())//
.andReturn().getResponse();
Links links = Links.valueOf(response.getHeader("Link"));
assertThat(links.hasLink("self"), is(true));
assertThat(links.hasLink("person"), is(true));
}
private List<Link> preparePersonResources(Person primary, Person... persons) throws Exception {
Link peopleLink = client.discoverUnique("people");
List<Link> links = new ArrayList<Link>();
MockHttpServletResponse primaryResponse = postAndGet(peopleLink, mapper.writeValueAsString(primary),
MediaType.APPLICATION_JSON);
links.add(client.assertHasLinkWithRel("siblings", primaryResponse));
for (Person person : persons) {
String payload = mapper.writeValueAsString(person);
MockHttpServletResponse response = postAndGet(peopleLink, payload, MediaType.APPLICATION_JSON);
links.add(client.assertHasLinkWithRel(Link.REL_SELF, response));
}
return links;
}
/**
* Asserts the {@link Person} resource the given link points to contains siblings with the given names.
*
* @param link
* @param siblingNames
* @throws Exception
*/
private void assertSiblingNames(Link link, String... siblingNames) throws Exception {
String responseBody = client.request(link).getContentAsString();
List<String> persons = JsonPath.read(responseBody, "$._embedded.people[*].firstName");
assertThat(persons, hasSize(siblingNames.length));
assertThat(persons, hasItems(siblingNames));
}
private void assertPersonWithNameAndSiblingLink(String name) throws Exception {
MockHttpServletResponse response = client.request(client.discoverUnique("people"));
String jsonPath = String.format("$._embedded.people[?(@.firstName == '%s')][0]", name);
// Assert content inlined
Object john = JsonPath.read(response.getContentAsString(), jsonPath);
assertThat(john, is(notNullValue()));
assertThat(JsonPath.read(john, "$.firstName"), is(notNullValue()));
// Assert sibling link exposed in resource pointed to
Link selfLink = new Link(JsonPath.<String> read(john, "$._links.self.href"));
client.follow(selfLink).//
andExpect(status().isOk()).//
andExpect(jsonPath("$._links.siblings", is(notNullValue())));
}
private static String toUriList(Link... links) {
List<String> uris = new ArrayList<String>(links.length);
for (Link link : links) {
uris.add(link.expand().getHref());
}
return StringUtils.collectionToDelimitedString(uris, "\n");
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2015-2016 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.jpa;
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.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.tests.TestMvcClient;
import org.springframework.data.rest.webmvc.ProfileController;
import org.springframework.data.rest.webmvc.ProfileResourceProcessor;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
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));
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2016 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.jpa;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class TestDataPopulator {
@Autowired PersonRepository people;
@Autowired OrderRepository orders;
@Autowired AuthorRepository authors;
@Autowired BookRepository books;
public void populateRepositories() {
books.deleteAll();
authors.deleteAll();
orders.deleteAll();
people.deleteAll();
populatePeople();
populateOrders();
populateAuthorsAndBooks();
}
private void populateAuthorsAndBooks() {
Author ollie = new Author("Ollie");
Author mark = new Author("Mark");
Author michael = new Author("Michael");
Author david = new Author("David");
Author john = new Author("John");
Author thomas = new Author("Thomas");
Iterable<Author> authors = this.authors.save(Arrays.asList(ollie, mark, michael, david, john, thomas));
books.save(new Book("1449323952", "Spring Data", authors));
books.save(new Book("1449323953", "Spring Data (Second Edition)", authors));
}
private void populateOrders() {
Person person = people.findAll().iterator().next();
Order order = new Order(person);
order.add(new LineItem("Java Chip"));
orders.save(order);
}
private void populatePeople() {
Person billyBob = people.save(new Person("Billy Bob", "Thornton"));
Person john = new Person("John", "Doe");
Person jane = new Person("Jane", "Doe");
john.addSibling(jane);
john.setFather(billyBob);
jane.addSibling(john);
jane.setFather(billyBob);
people.save(Arrays.asList(john, jane));
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2016 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.jpa.groovy;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* Simulates a Groovy domain object by extending {@link GroovyObject}.
*
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-754
*/
@Entity
public class SimulatedGroovyDomainClass implements GroovyObject {
private @Id @GeneratedValue Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//
// The following fields don't actually have to be implemented since the test cases don't
// make any Groovy calls. This just simulates the structure of a Groovy object to
// verify proper handling.
//
@Override
public Object invokeMethod(String s, Object o) {
return null;
}
@Override
public Object getProperty(String s) {
return null;
}
@Override
public void setProperty(String s, Object o) {}
@Override
public MetaClass getMetaClass() {
return null;
}
@Override
public void setMetaClass(MetaClass metaClass) {}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 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.jpa.groovy;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.webmvc.jpa.groovy.SimulatedGroovyDomainClass;
/**
* Simulates a repository built on a Groovy domain object.
*
* @author Greg Turnquist
* @see DATAREST-754
*/
public interface SimulatedGroovyDomainClassRepository extends CrudRepository<SimulatedGroovyDomainClass, Long> {}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2015-2016 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.json;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.persistence.EntityManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.OrderRepository;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Integration tests for {@link Jackson2DatatypeHelper}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { JpaRepositoryConfig.class, RepositoryRestMvcConfiguration.class })
@Transactional
public class Jackson2DatatypeHelperIntegrationTests {
@Autowired PersistentEntities entities;
@Autowired ObjectMapper objectMapper;
@Autowired PersonRepository people;
@Autowired OrderRepository orders;
@Autowired EntityManager em;
Order order;
@Before
public void setUp() {
this.order = orders.save(new Order(people.save(new Person("Dave", "Matthews"))));
// Reset JPA to make sure the query returns a result with proxy references
em.flush();
em.clear();
}
/**
* @see DATAREST-500
*/
@Test
public void configuresHIbernate4ModuleToLoadLazyLoadingProxies() throws Exception {
PersistentEntity<?, ?> entity = entities.getPersistentEntity(Order.class);
PersistentProperty<?> property = entity.getPersistentProperty("creator");
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(orders.findOne(this.order.getId()));
assertThat(objectMapper.writeValueAsString(accessor.getProperty(property)), is(not("null")));
}
}

View File

@@ -0,0 +1,292 @@
/*
* Copyright 2012-2016 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.json;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.LineItem;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.OrderRepository;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.data.rest.webmvc.jpa.PersonSummary;
import org.springframework.data.rest.webmvc.jpa.UserExcerpt;
import org.springframework.data.rest.webmvc.util.TestUtils;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.hateoas.core.EmbeddedWrappers;
import org.springframework.hateoas.hal.HalLinkDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.util.UriTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for entity (de)serialization.
*
* @author Jon Brisbin
* @author Greg Turnquist
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { JpaRepositoryConfig.class, PersistentEntitySerializationTests.TestConfig.class })
@Transactional
public class PersistentEntitySerializationTests {
private static final String PERSON_JSON_IN = "{\"firstName\": \"John\",\"lastName\": \"Doe\"}";
@Autowired ObjectMapper mapper;
@Autowired Repositories repositories;
@Autowired PersonRepository people;
@Autowired OrderRepository orders;
@Configuration
static class TestConfig extends RepositoryTestsConfig {
@Bean
@Override
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = super.objectMapper();
objectMapper.registerModule(
new JacksonSerializers(new EnumTranslator(new MessageSourceAccessor(new StaticMessageSource()))));
return objectMapper;
}
}
LinkDiscoverer linkDiscoverer;
ProjectionFactory projectionFactory;
@Before
public void setUp() {
RequestContextHolder.setRequestAttributes(new ServletWebRequest(new MockHttpServletRequest()));
this.linkDiscoverer = new HalLinkDiscoverer();
this.projectionFactory = new SpelAwareProxyProjectionFactory();
}
@Test
public void deserializesPersonEntity() throws IOException {
Person p = mapper.readValue(PERSON_JSON_IN, Person.class);
assertThat(p.getFirstName(), is("John"));
assertThat(p.getLastName(), is("Doe"));
assertThat(p.getSiblings(), is(Collections.EMPTY_LIST));
}
/**
* @see DATAREST-238
*/
@Test
public void deserializePersonWithLinks() throws IOException {
String bilbo = "{\n" + " \"_links\" : {\n" + " \"self\" : {\n"
+ " \"href\" : \"http://localhost/people/4\"\n" + " },\n" + " \"siblings\" : {\n"
+ " \"href\" : \"http://localhost/people/4/siblings\"\n" + " },\n" + " \"father\" : {\n"
+ " \"href\" : \"http://localhost/people/4/father\"\n" + " }\n" + " },\n"
+ " \"firstName\" : \"Bilbo\",\n" + " \"lastName\" : \"Baggins\",\n"
+ " \"created\" : \"2014-01-31T21:07:45.574+0000\"\n" + "}\n";
Person p = mapper.readValue(bilbo, Person.class);
assertThat(p.getFirstName(), equalTo("Bilbo"));
assertThat(p.getLastName(), equalTo("Baggins"));
}
/**
* @see DATAREST-238
*/
@Test
public void serializesPersonEntity() throws IOException, InterruptedException {
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(Person.class);
Person person = people.save(new Person("John", "Doe"));
PersistentEntityResource resource = PersistentEntityResource.build(person, persistentEntity).//
withLink(new Link("/person/" + person.getId())).build();
StringWriter writer = new StringWriter();
mapper.writeValue(writer, resource);
String s = writer.toString();
Link fatherLink = linkDiscoverer.findLinkWithRel("father", s);
assertThat(fatherLink.getHref(), endsWith(new UriTemplate("/{id}/father").expand(person.getId()).toString()));
Link siblingLink = linkDiscoverer.findLinkWithRel("siblings", s);
assertThat(siblingLink.getHref(), endsWith(new UriTemplate("/{id}/siblings").expand(person.getId()).toString()));
}
/**
* @see DATAREST-248
*/
@Test
public void deserializesPersonWithLinkToOtherPersonCorrectly() throws Exception {
Person father = people.save(new Person("John", "Doe"));
String child = String.format("{ \"firstName\" : \"Bilbo\", \"father\" : \"/persons/%s\"}", father.getId());
Person result = mapper.readValue(child, Person.class);
assertThat(result.getFather(), is(father));
}
/**
* @see DATAREST-248
*/
@Test
public void deserializesPersonWithLinkToOtherPersonsCorrectly() throws Exception {
Person firstSibling = people.save(new Person("John", "Doe"));
Person secondSibling = people.save(new Person("Dave", "Doe"));
String child = String.format("{ \"firstName\" : \"Bilbo\", \"siblings\" : [\"/persons/%s\", \"/persons/%s\"]}",
firstSibling.getId(), secondSibling.getId());
Person result = mapper.readValue(child, Person.class);
assertThat(result.getSiblings(), hasItems(firstSibling, secondSibling));
}
/**
* @see DATAREST-248
*/
@Test
public void deserializesEmbeddedAssociationsCorrectly() throws Exception {
String content = TestUtils.readFileFromClasspath("order.json");
Order order = mapper.readValue(content, Order.class);
assertThat(order.getLineItems(), hasSize(2));
}
/**
* @see DATAREST-250
*/
@Test
public void serializesReferencesWithinPagedResourceCorrectly() throws Exception {
Person creator = new Person("Dave", "Matthews");
Order order = new Order(creator);
order.add(new LineItem("first"));
order.add(new LineItem("second"));
PersistentEntityResource orderResource = PersistentEntityResource.//
build(order, repositories.getPersistentEntity(Order.class)).//
withLink(new Link("/orders/1")).//
build();
PagedResources<PersistentEntityResource> persistentEntityResource = new PagedResources<PersistentEntityResource>(
Arrays.asList(orderResource), new PageMetadata(1, 0, 10));
String result = mapper.writeValueAsString(persistentEntityResource);
assertThat(JsonPath.read(result, "$_embedded.orders[*].lineItems"), is(notNullValue()));
}
/**
* @see DATAREST-521
*/
@Test
public void serializesLinksForExcerpts() throws Exception {
Person dave = new Person("Dave", "Matthews");
dave.setId(1L);
Person oliver = new Person("Oliver August", "Matthews");
oliver.setId(2L);
oliver.setFather(dave);
UserExcerpt daveExcerpt = projectionFactory.createProjection(UserExcerpt.class, dave);
EmbeddedWrapper wrapper = new EmbeddedWrappers(false).wrap(daveExcerpt, "father");
PersistentEntityResource resource = PersistentEntityResource.//
build(oliver, repositories.getPersistentEntity(Person.class)).//
withEmbedded(Arrays.asList(wrapper)).//
build();
String result = mapper.writeValueAsString(resource);
assertThat(JsonPath.read(result, "$_embedded.father[*]._links.self"), is(notNullValue()));
}
/**
* @see DATAREST-521
*/
@Test
public void rendersAdditionalLinksRegisteredWithResource() throws Exception {
Person dave = new Person("Dave", "Matthews");
PersistentEntityResource resource = PersistentEntityResource.//
build(dave, repositories.getPersistentEntity(Person.class)).//
withLink(new Link("/people/1")).//
withLink(new Link("/aditional", "processed")).//
build();
String result = mapper.writeValueAsString(resource);
assertThat(JsonPath.read(result, "$_links.processed"), is(notNullValue()));
}
/**
* @see DATAREST-697
*/
@Test
public void rendersProjectionWithinSimpleResourceCorrectly() throws Exception {
Person person = new Person("Dave", "Matthews");
person.setId(1L);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
PersonSummary projection = factory.createProjection(PersonSummary.class, person);
String result = mapper.writeValueAsString(new Resource<PersonSummary>(projection));
assertThat(JsonPath.read(result, "$._links.self"), is(notNullValue()));
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2012-2016 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.json;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
import org.springframework.data.rest.core.config.MetadataConfiguration;
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.support.DefaultSelfLinkProvider;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
import org.springframework.data.rest.webmvc.ResourceProcessorInvoker;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.mapping.LinkCollector;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
import org.springframework.data.rest.webmvc.support.PagingAndSortingTemplateVariables;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Jon Brisbin
* @author Greg Turnquist
* @author Oliver Gierke
*/
@Configuration
@SuppressWarnings("deprecation")
public class RepositoryTestsConfig {
@Autowired ApplicationContext appCtx;
@Autowired(required = false) List<MappingContext<?, ?>> mappingContexts = Collections.emptyList();
@Bean
public Repositories repositories() {
return new Repositories(appCtx);
}
@Bean
public RepositoryRestConfiguration config() {
RepositoryRestConfiguration config = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
config.setResourceMappingForDomainType(Person.class).setRel("person");
config.setResourceMappingForRepository(PersonRepository.class).setRel("people").setPath("people")
.addResourceMappingFor("findByFirstName").setRel("firstname").setPath("firstname");
return config;
}
@Bean
public DefaultFormattingConversionService defaultConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
DomainClassConverter<FormattingConversionService> converter = new DomainClassConverter<FormattingConversionService>(
conversionService);
converter.setApplicationContext(appCtx);
return conversionService;
}
@Bean
public PersistentEntities persistentEntities() {
return new PersistentEntities(mappingContexts);
}
@Bean
public Module persistentEntityModule() {
RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(),
config().getRepositoryDetectionStrategy());
EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(),
mock(PagingAndSortingTemplateVariables.class),
OrderAwarePluginRegistry.<Class<?>, BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE)));
SelfLinkProvider selfLinkProvider = new DefaultSelfLinkProvider(persistentEntities(), entityLinks,
Collections.<EntityLookup<?>> emptyList());
DefaultRepositoryInvokerFactory invokerFactory = new DefaultRepositoryInvokerFactory(repositories());
UriToEntityConverter uriToEntityConverter = new UriToEntityConverter(persistentEntities(), invokerFactory,
repositories());
Associations associations = new Associations(mappings, config());
LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations);
NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities(),
new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class)),
new ResourceProcessorInvoker(Collections.<ResourceProcessor<?>> emptyList()));
return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector,
invokerFactory, nestedEntitySerializer, mock(LookupObjectSerializer.class));
}
@Bean
public ObjectMapper objectMapper() {
RelProvider relProvider = new EvoInflectorRelProvider();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.registerModule(persistentEntityModule());
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null));
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_EMPTY);
return mapper;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2014-2016 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.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Integration tests for {@link BackendIdHandlerMethodArgumentResolver}.
*
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class BackendIdConverterHandlerMethodArgumentResolverIntegrationTests
extends AbstractControllerIntegrationTests {
@Autowired BackendIdHandlerMethodArgumentResolver resolver;
/**
* @see DATAREST-155
*/
@Test
public void translatesUriToBackendId() throws Exception {
Method method = ReflectionUtils.findMethod(SampleController.class, "resolveId", Serializable.class);
MethodParameter parameter = new MethodParameter(method, 0);
NativeWebRequest request = new ServletWebRequest(new MockHttpServletRequest("GET", "/books/5-5-5-5-5"));
Object resolvedId = resolver.resolveArgument(parameter, null, request, null);
assertThat(resolvedId, is((Object) 5L));
}
static class SampleController {
@RequestMapping("/{repository}/{id}")
void resolveId(@BackendId Serializable backendId) {}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2014-2016 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.support;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.rest.tests.AbstractWebIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Integration tests for customization of Spring Data REST's exception handling.
*
* @author Thibaud Lepretre
* @author Oliver Gierke
*/
@ContextConfiguration
public class ExceptionHandlingCustomizationIntegrationTests extends AbstractWebIntegrationTests {
@Configuration
@Import(JpaRepositoryConfig.class)
static class ControllerAdviceConfig {
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
static class CustomGlobalConfiguration {
@ExceptionHandler
ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
HttpHeaders headers = new HttpHeaders();
headers.setAllow(o_O.getSupportedHttpMethods());
return new ResponseEntity<Void>(headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
@Test
public void httpRequestMethodNotSupportedExceptionShouldNowReturnHttpStatus500Over405() throws Exception {
Link link = client.discoverUnique("addresses");
mvc.perform(get(link.getHref())).//
andExpect(status().isInternalServerError());
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2014-2016 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.support;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.webmvc.jpa.Book;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Integration tests for {@link RepositoryEntityLinks}.
*
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class RepositoryEntityLinksIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositoryRestConfiguration configuration;
@Autowired RepositoryEntityLinks entityLinks;
@Test
public void returnsLinkToSingleResource() {
Link link = entityLinks.linkToSingleResource(Person.class, 1);
assertThat(link.getHref(), endsWith("/people/1{?projection}"));
assertThat(link.getRel(), is("person"));
}
@Test
public void returnsTemplatedLinkForPagingResource() {
Link link = entityLinks.linkToCollectionResource(Person.class);
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItems("page", "size", "sort"));
assertThat(link.getRel(), is("people"));
}
/**
* @see DATAREST-221
*/
@Test
public void returnsLinkWithProjectionTemplateVariableIfProjectionIsDefined() {
Link link = entityLinks.linkToSingleResource(Order.class, 1);
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItem(configuration.getProjectionConfiguration().getParameterName()));
}
/**
* @see DATAREST-155
*/
@Test
public void usesCustomGeneratedBackendId() {
Link link = entityLinks.linkToSingleResource(Book.class, 7L);
assertThat(link.expand().getHref(), endsWith("/7-7-7-7-7-7-7"));
}
/**
* @see DATAREST-317
*/
@Test
public void adaptsToExistingPageable() {
Link link = entityLinks.linkToPagedResource(Person.class, new PageRequest(0, 10));
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasSize(2));
assertThat(link.getVariableNames(), hasItems("sort", "projection"));
}
/**
* @see DATAREST-467
*/
@Test
public void returnsLinksToSearchResources() {
Links links = entityLinks.linksToSearchResources(Person.class);
assertThat(links.hasLink("firstname"), is(true));
Link firstnameLink = links.getLink("firstname");
assertThat(firstnameLink.isTemplated(), is(true));
assertThat(firstnameLink.getVariableNames(), hasItems("page", "size"));
}
/**
* @see DATAREST-467
*/
@Test
public void returnsLinkToSearchResource() {
Link link = entityLinks.linkToSearchResource(Person.class, "firstname");
assertThat(link, is(notNullValue()));
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItems("firstname", "page", "size"));
}
/**
* @see DATAREST-467
* @see DATAREST-519
*/
@Test
public void prepopulatesPaginationInformationForSearchResourceLink() {
Link link = entityLinks.linkToSearchResource(Person.class, "firstname", new PageRequest(0, 10));
assertThat(link, is(notNullValue()));
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItem("firstname"));
assertThat(link.getVariableNames(), not(hasItems("page", "size")));
UriComponents components = UriComponentsBuilder.fromUriString(link.getHref()).build();
assertThat(components.getQueryParams(), allOf(hasKey("page"), hasKey("size")));
}
/**
* @see DATAREST-467
*/
@Test
public void returnsTemplatedLinkForSortedSearchResource() {
Link link = entityLinks.linkToSearchResource(Person.class, "lastname");
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItems("lastname", "sort"));
}
/**
* @see DATAREST-467
* @see DATAREST-519
*/
@Test
public void prepopulatesSortInformationForSearchResourceLink() {
Link link = entityLinks.linkToSearchResource(Person.class, "lastname", new Sort("firstname"));
assertThat(link, is(notNullValue()));
assertThat(link.isTemplated(), is(true));
assertThat(link.getVariableNames(), hasItem("lastname"));
assertThat(link.getVariableNames(), not(hasItems("sort")));
UriComponents components = UriComponentsBuilder.fromUriString(link.getHref()).build();
assertThat(components.getQueryParams(), hasKey("sort"));
}
/**
* @see DATAREST-668
* @see DATAREST-519
* @see DATAREST-467
*/
@Test
public void addsProjectVariableToSearchResourceIfAvailable() {
for (Link link : entityLinks.linksToSearchResources(Book.class)) {
assertThat(link.getVariableNames(), hasItem("projection"));
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2016 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.util;
import java.nio.charset.Charset;
import java.util.Scanner;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.rest.webmvc.jpa.JpaWebTests;
/**
* Test helper methods.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class TestUtils {
private static final Charset UTF8 = Charset.forName("UTF-8");
public static String readFileFromClasspath(String name) throws Exception {
ClassPathResource file = new ClassPathResource(name, JpaWebTests.class);
StringBuilder builder = new StringBuilder();
Scanner scanner = new Scanner(file.getFile(), UTF8.name());
try {
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine());
}
} finally {
scanner.close();
}
return builder.toString();
}
}

View File

@@ -0,0 +1,15 @@
{
"_links": {
"self": {
"href": "http://localhost:8080/persons/1"
}
},
"lineItems": [
{
"name": "Java Chip"
},
{
"name": "Chocolate Mocca "
}
]
}

View File

@@ -0,0 +1,3 @@
{ "firstName" : "Dave",
"lastName" : "Matthews"
}

View File

@@ -0,0 +1,90 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests - MongoDB</name>
<artifactId>spring-data-rest-tests-mongodb</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests-core</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<type>test-jar</type>
</dependency>
<!-- MongoDB -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>${springdata.mongodb}</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>${querydsl}</version>
<scope>test</scope>
</dependency>
<!-- Querydsl -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
<version>${querydsl}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt}</version>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>sources</id>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/annotations</outputDirectory>
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
<logOnlyOnError>true</logOnlyOnError>
<options>
<querydsl.excludedPackages>org.springframework.data.rest.tests.mongodb.groovy,groovy.lang</querydsl.excludedPackages>
</options>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2013-2016 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.tests.mongodb;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Oliver Gierke
*/
public class Address {
public String street;
public @JsonProperty(required = true) String zipCode;
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2016 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.tests.mongodb;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.Document;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Jon Brisbin
*/
@Document
public class Profile {
@Id private String id;
private Long person;
private @JsonProperty(required = true) String type;
private @LastModifiedDate Date lastModifiedDate;
private @JsonProperty("renamed") String aliased;
private Map<String, String> metadata = new HashMap<String, String>();
public String getId() {
return id;
}
public Profile setId(String id) {
this.id = id;
return this;
}
public Long getPerson() {
return person;
}
public Profile setPerson(Long person) {
this.person = person;
return this;
}
public String getType() {
return type;
}
public Profile setType(String type) {
this.type = type;
return this;
}
@JsonIgnore
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public String getAliased() {
return aliased;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2016 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.tests.mongodb;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public interface ProfileRepository extends PagingAndSortingRepository<Profile, String> {
List<Profile> findByType(String type);
/**
* @see DATAREST-247
*/
long countByType(@Param("type") String type);
/**
* @see DATAREST-511
*/
Optional<Profile> findById(@Param("id") String id);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2014-2016 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.tests.mongodb;
import java.math.BigDecimal;
import java.util.Date;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* @author Pablo Lozano
*/
// tag::code[]
@Document
public class Receipt {
public @Id String id;
public @Version Long version;
public @LastModifiedDate Date date; // <1>
public String saleItem;
public BigDecimal amount;
}
// end::code[]

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014-2016 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.tests.mongodb;
import org.springframework.data.repository.CrudRepository;
/**
* A repository to manage {@link Receipt}s.
*
* @author Pablo Lozano
*/
public interface ReceiptRepository extends CrudRepository<Receipt, String> {
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2016 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.tests.mongodb;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* @author Oliver Gierke
*/
@Document
public class User {
public static enum Gender {
MALE, FEMALE;
}
public BigInteger id;
public String firstname, lastname;
public Address address;
public Set<Address> shippingAddresses;
public List<String> nicknames;
public Gender gender;
public @ReadOnlyProperty EmailAddress email;
public LocalDateTime java8DateTime;
public org.joda.time.LocalDateTime jodaDateTime;
public TypeWithPattern pattern;
public @DBRef(lazy = true) List<User> colleagues;
public static class EmailAddress {
private final String value;
/**
* @param value
*/
public EmailAddress(String value) {
this.value = value;
}
@Override
@JsonValue
public String toString() {
return value;
}
}
public static class TypeWithPattern {}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2016 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.tests.mongodb;
import java.math.BigInteger;
import java.util.List;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
/**
* @author Oliver Gierke
*/
public interface UserRepository extends CrudRepository<User, BigInteger>, QueryDslPredicateExecutor<User> {
List<User> findByFirstname(String firstname);
List<User> findByColleaguesContains(@Param("colleagues") User colleague);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2015-2016 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.tests.mongodb;
import org.springframework.data.rest.core.config.Projection;
/**
* @author Oliver Gierke
*/
@Projection(types = User.class)
public interface UserSummary {
String getFirstname();
String getLastname();
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2016 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.tests.mongodb.groovy;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* Simulates a Groovy domain object by extending {@link GroovyObject}.
*
* @author Greg Turnquist
* @author Oliver Gierke
* @see DATAREST-754
*/
@Document
public class SimulatedGroovyDomainClass implements GroovyObject {
private String id, name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//
// The following fields don't actually have to be implemented since the test cases don't
// make any Groovy calls. This just simulates the structure of a Groovy object to
// verify proper handling.
//
@Override
public Object invokeMethod(String s, Object o) {
return null;
}
@Override
public Object getProperty(String s) {
return null;
}
@Override
public void setProperty(String s, Object o) {}
@Override
public MetaClass getMetaClass() {
return null;
}
@Override
public void setMetaClass(MetaClass metaClass) {}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2016-2016 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.tests.mongodb.groovy;
import org.springframework.data.repository.CrudRepository;
/**
* Simulates a repository built on a Groovy domain object.
*
* @author Greg Turnquist
* @see DATAREST-754
*/
public interface SimulatedGroovyDomainClassRepository extends CrudRepository<SimulatedGroovyDomainClass, String> {}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2016 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.tests.mongodb;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@Configuration
@EnableMongoRepositories
@EnableMongoAuditing
public class MongoDbRepositoryConfig extends AbstractMongoConfiguration {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#getDatabaseName()
*/
@Override
protected String getDatabaseName() {
return "spring-data-rest-sample";
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#getMappingBasePackage()
*/
@Override
protected String getMappingBasePackage() {
return "";
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#mongo()
*/
@Override
public Mongo mongo() throws Exception {
return new MongoClient();
}
}

View File

@@ -0,0 +1,352 @@
/*
* Copyright 2013-2016 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.tests.mongodb;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.http.HttpHeaders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.tests.CommonWebTests;
import org.springframework.data.rest.tests.mongodb.Address;
import org.springframework.data.rest.tests.mongodb.Profile;
import org.springframework.data.rest.tests.mongodb.ProfileRepository;
import org.springframework.data.rest.tests.mongodb.Receipt;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.data.rest.tests.mongodb.UserRepository;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for MongoDB repositories.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@ContextConfiguration(classes = MongoDbRepositoryConfig.class)
public class MongoWebTests extends CommonWebTests {
@Autowired ProfileRepository repository;
@Autowired UserRepository userRepository;
@Autowired RepositoryEntityLinks entityLinks;
ObjectMapper mapper = new ObjectMapper();
@Before
public void populateProfiles() {
mapper.setSerializationInclusion(Include.NON_NULL);
Profile twitter = new Profile();
twitter.setPerson(1L);
twitter.setType("Twitter");
Profile linkedIn = new Profile();
linkedIn.setPerson(1L);
linkedIn.setType("LinkedIn");
repository.save(Arrays.asList(twitter, linkedIn));
Address address = new Address();
address.street = "ETagDoesntMatchExceptionUnitTests";
address.zipCode = "Bar";
User thomas = new User();
thomas.firstname = "Thomas";
thomas.lastname = "Darimont";
thomas.address = address;
userRepository.save(thomas);
User oliver = new User();
oliver.firstname = "Oliver";
oliver.lastname = "Gierke";
oliver.address = address;
oliver.colleagues = Arrays.asList(thomas);
userRepository.save(oliver);
thomas.colleagues = Arrays.asList(oliver);
userRepository.save(thomas);
}
@After
public void cleanUp() {
repository.deleteAll();
userRepository.deleteAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#expectedRootLinkRels()
*/
@Override
protected Iterable<String> expectedRootLinkRels() {
return Arrays.asList("profiles", "users");
}
@Test
public void foo() throws Exception {
Link profileLink = client.discoverUnique("profiles");
client.follow(profileLink).//
andExpect(jsonPath("$._embedded.profiles").value(hasSize(2)));
}
@Test
public void rendersEmbeddedDocuments() throws Exception {
Link usersLink = client.discoverUnique("users");
Link userLink = assertHasContentLinkWithRel("self", client.request(usersLink));
client.follow(userLink).//
andExpect(jsonPath("$.address.zipCode").value(is(notNullValue())));
}
/**
* @see DATAREST-247
*/
@Test
public void executeQueryMethodWithPrimitiveReturnType() throws Exception {
Link profiles = client.discoverUnique("profiles");
Link profileSearches = client.discoverUnique(profiles, "search");
Link countByTypeLink = client.discoverUnique(profileSearches, "countByType");
assertThat(countByTypeLink.isTemplated(), is(true));
assertThat(countByTypeLink.getVariableNames(), hasItem("type"));
MockHttpServletResponse response = client.request(countByTypeLink.expand("Twitter"));
assertThat(response.getContentAsString(), is("1"));
}
@Test
public void testname() throws Exception {
Link usersLink = client.discoverUnique("users");
Link userLink = assertHasContentLinkWithRel("self", client.request(usersLink));
MockHttpServletResponse response = patchAndGet(userLink,
"{\"lastname\" : null, \"address\" : { \"zipCode\" : \"ZIP\"}}", MediaType.APPLICATION_JSON);
assertThat(JsonPath.read(response.getContentAsString(), "$.lastname"), is(nullValue()));
assertThat(JsonPath.read(response.getContentAsString(), "$.address.zipCode"), is((Object) "ZIP"));
}
@Test
public void testname2() throws Exception {
Link usersLink = client.discoverUnique("users");
Link userLink = assertHasContentLinkWithRel("self", client.request(usersLink));
MockHttpServletResponse response = patchAndGet(userLink,
"[{ \"op\": \"replace\", \"path\": \"/address/zipCode\", \"value\": \"ZIP\" },"
// + "{ \"op\": \"replace\", \"path\": \"/lastname\", \"value\": null }]", //
+ "{ \"op\": \"remove\", \"path\": \"/lastname\" }]", //
RestMediaTypes.JSON_PATCH_JSON);
assertThat(JsonPath.read(response.getContentAsString(), "$.lastname"), is(nullValue()));
assertThat(JsonPath.read(response.getContentAsString(), "$.address.zipCode"), is((Object) "ZIP"));
}
/**
* @see DATAREST-160
*/
@Test
public void returnConflictWhenConcurrentlyEditingVersionedEntity() throws Exception {
Link receiptLink = client.discoverUnique("receipts");
Receipt receipt = new Receipt();
receipt.amount = new BigDecimal(50);
receipt.saleItem = "Springy Tacos";
String stringReceipt = mapper.writeValueAsString(receipt);
MockHttpServletResponse createdReceipt = postAndGet(receiptLink, stringReceipt, MediaType.APPLICATION_JSON);
Link tacosLink = client.assertHasLinkWithRel("self", createdReceipt);
assertJsonPathEquals("$.saleItem", "Springy Tacos", createdReceipt);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(tacosLink.getHref());
String concurrencyTag = createdReceipt.getHeader("ETag");
mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyBurritos\" }")
.contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, concurrencyTag))
.andExpect(status().is2xxSuccessful());
mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyTequila\" }")
.contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, concurrencyTag))
.andExpect(status().isPreconditionFailed());
}
/**
* @see DATAREST-471
*/
@Test
public void auditableResourceHasLastModifiedHeaderSet() throws Exception {
Profile profile = repository.findAll().iterator().next();
String header = mvc.perform(get("/profiles/{id}", profile.getId())).//
andReturn().getResponse().getHeader("Last-Modified");
assertThat(header, not(isEmptyOrNullString()));
}
/**
* @see DATAREST-482
*/
@Test
public void putDoesNotRemoveAssociations() throws Exception {
Link usersLink = client.discoverUnique("users");
Link userLink = assertHasContentLinkWithRel("self", client.request(usersLink));
Link colleaguesLink = client.assertHasLinkWithRel("colleagues", client.request(userLink));
// Expect a user returned as colleague
client.follow(colleaguesLink).//
andExpect(jsonPath("$._embedded.users").exists());
User oliver = new User();
oliver.firstname = "Oliver";
oliver.lastname = "Gierke";
putAndGet(userLink, mapper.writeValueAsString(oliver), MediaType.APPLICATION_JSON);
// Expect colleague still present but address has been wiped
client.follow(colleaguesLink).//
andExpect(jsonPath("$._embedded.users").exists()).//
andExpect(jsonPath("$.embedded.users[0].address").doesNotExist());
}
/**
* @see DATAREST-482
*/
@Test
public void emptiesAssociationForEmptyUriList() throws Exception {
Link usersLink = client.discoverUnique("users");
Link userLink = assertHasContentLinkWithRel("self", client.request(usersLink));
Link colleaguesLink = client.assertHasLinkWithRel("colleagues", client.request(userLink));
putAndGet(colleaguesLink, "", MediaType.parseMediaType("text/uri-list"));
client.follow(colleaguesLink).//
andExpect(status().isOk()).//
andExpect(jsonPath("$").exists());
}
/**
* @see DATAREST-491
*/
@Test
public void updatesMapPropertyCorrectly() throws Exception {
Link profilesLink = client.discoverUnique("profiles");
Link profileLink = assertHasContentLinkWithRel("self", client.request(profilesLink));
Profile profile = new Profile();
profile.setMetadata(Collections.singletonMap("Key", "Value"));
putAndGet(profileLink, mapper.writeValueAsString(profile), MediaType.APPLICATION_JSON);
client.follow(profileLink).andExpect(jsonPath("$.metadata.Key").value("Value"));
}
/**
* @see DATAREST-506
*/
@Test
public void supportsConditionalGetsOnItemResource() throws Exception {
Receipt receipt = new Receipt();
receipt.amount = new BigDecimal(50);
receipt.saleItem = "Springy Tacos";
Link receiptsLink = client.discoverUnique("receipts");
MockHttpServletResponse response = postAndGet(receiptsLink, mapper.writeValueAsString(receipt),
MediaType.APPLICATION_JSON);
Link receiptLink = client.getDiscoverer(response).findLinkWithRel("self", response.getContentAsString());
mvc.perform(get(receiptLink.getHref()).header(IF_MODIFIED_SINCE, response.getHeader(LAST_MODIFIED))).//
andExpect(status().isNotModified()).//
andExpect(header().string(ETAG, is(notNullValue())));
mvc.perform(get(receiptLink.getHref()).header(IF_NONE_MATCH, response.getHeader(ETAG))).//
andExpect(status().isNotModified()).//
andExpect(header().string(ETAG, is(notNullValue())));
}
/**
* @see DATAREST-511
*/
@Test
public void invokesQueryResourceReturningAnOptional() throws Exception {
Profile profile = repository.findAll().iterator().next();
Link link = client.discoverUnique("profiles", "search", "findById");
mvc.perform(get(link.expand(profile.getId()).getHref())).//
andExpect(status().isOk());
}
/**
* @see DATAREST-517
*/
@Test
public void returnsNotFoundIfQueryExecutionDoesNotReturnResult() throws Exception {
Link link = client.discoverUnique("profiles", "search", "findById");
mvc.perform(get(link.expand("").getHref())).//
andExpect(status().isNotFound());
}
/**
* @see DATAREST-712
*/
@Test
public void invokesQueryMethodTakingAReferenceCorrectly() throws Exception {
Link link = client.discoverUnique("users", "search", "findByColleaguesContains");
User thomas = userRepository.findAll(QUser.user.firstname.eq("Thomas")).iterator().next();
Link thomasUri = entityLinks.linkToSingleResource(User.class, thomas.id).expand();
String href = link.expand(thomasUri.getHref()).getHref();
mvc.perform(get(href)).andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2016 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.tests.mongodb;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.springframework.util.Assert;
/**
* Test helper methods.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class TestUtils {
private static final Charset UTF8 = Charset.forName("UTF-8");
/**
* Returns the given {@link String} as {@link InputStream}.
*
* @param source must not be {@literal null}.
* @return
*/
public static InputStream asStream(String source) {
Assert.notNull(source, "Source string must not be null!");
return new ByteArrayInputStream(source.getBytes(UTF8));
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2015-2016 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.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.math.BigInteger;
import java.util.Collections;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.mockito.internal.stubbing.answers.ReturnsArgumentAt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.support.DefaultSelfLinkProvider;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests.TestConfiguration;
import org.springframework.data.rest.tests.mongodb.MongoDbRepositoryConfig;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.support.Projector;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.test.context.ContextConfiguration;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Integration tests for {@link PersistentEntityResourceAssembler}.
*
* @author Oliver Gierke
*/
@ContextConfiguration(classes = { TestConfiguration.class, MongoDbRepositoryConfig.class })
public class PersistentEntityResourceAssemblerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired PersistentEntities entities;
@Autowired EntityLinks entityLinks;
@Autowired @Qualifier("objectMapper") ObjectMapper objectMapper;
@Autowired Associations associations;
/**
* @see DATAREST-609
*/
@Test
public void addsSelfAndSingleResourceLinkToResourceByDefault() throws Exception {
Projector projector = mock(Projector.class);
when(projector.projectExcerpt(anyObject())).thenAnswer(new ReturnsArgumentAt(0));
PersistentEntityResourceAssembler assembler = new PersistentEntityResourceAssembler(entities, projector,
associations, new DefaultSelfLinkProvider(entities, entityLinks, Collections.<EntityLookup<?>> emptyList()));
User user = new User();
user.id = BigInteger.valueOf(4711);
PersistentEntityResource resource = assembler.toResource(user);
Links links = new Links(resource.getLinks());
assertThat(links, is(Matchers.<Link> iterableWithSize(2)));
assertThat(links.getLink("self").getVariables(), is(Matchers.empty()));
assertThat(links.getLink("user").getVariableNames(), is(hasItem("projection")));
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2015-2016 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 org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
import org.springframework.data.rest.tests.mongodb.MongoDbRepositoryConfig;
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
/**
* Integration tests for {@link BasePathAwareHandlerMapping}.
*
* @author Oliver Gierke
* @soundtrack Elephants Crossing - Echo (Irrelephant)
*/
@ContextConfiguration(classes = MongoDbRepositoryConfig.class)
public class RepositoryRestHandlerMappingIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired DelegatingHandlerMapping mapping;
/**
* @see DATAREST-617
*/
@Test
public void usesMethodsWithoutProducesClauseForGeneralJsonRequests() throws Exception {
MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", "/users");
mockRequest.addHeader("Accept", "application/*+json");
HandlerExecutionChain chain = mapping.getHandler(mockRequest);
assertThat(chain, is(notNullValue()));
Object handler = chain.getHandler();
assertThat(handler, is(instanceOf(HandlerMethod.class)));
HandlerMethod method = (HandlerMethod) handler;
assertThat(method.getMethod().getDeclaringClass(), is(typeCompatibleWith(RepositoryEntityController.class)));
assertThat(method.getMethod().getName(), is("getCollectionResource"));
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2016 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.config;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
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;
/**
* Base class for integration tests that are run against a particular configuration defined by the subclass.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public abstract class AbstractRepositoryRestMvcConfigurationIntegrationTests {
@Autowired WebApplicationContext context;
protected MockMvc mvc;
@Before
public void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2014-2016 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.config;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.rest.tests.mongodb.TestUtils.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.tests.mongodb.Address;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.data.rest.webmvc.json.DomainObjectReader;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.http.converter.HttpMessageNotReadableException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Unit tests for {@link JsonPatchHandler}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class JsonPatchHandlerUnitTests {
JsonPatchHandler handler;
User user;
@Mock ResourceMappings mappings;
public @Rule ExpectedException exception = ExpectedException.none();
@Before
public void setUp() {
MongoMappingContext context = new MongoMappingContext();
context.getPersistentEntity(User.class);
PersistentEntities entities = new PersistentEntities(Arrays.asList(context));
Associations associations = new Associations(mappings, mock(RepositoryRestConfiguration.class));
this.handler = new JsonPatchHandler(new ObjectMapper(), new DomainObjectReader(entities, associations));
Address address = new Address();
address.street = "Foo";
address.zipCode = "Bar";
this.user = new User();
this.user.firstname = "Oliver";
this.user.lastname = "Gierke";
this.user.address = address;
}
/**
* @see DATAREST-348
*/
@Test
public void appliesRemoveOperationCorrectly() throws Exception {
String input = "[{ \"op\": \"replace\", \"path\": \"/address/zipCode\", \"value\": \"ZIP\" },"
+ "{ \"op\": \"remove\", \"path\": \"/lastname\" }]";
User result = handler.applyPatch(asStream(input), user);
assertThat(result.lastname, is(nullValue()));
assertThat(result.address.zipCode, is("ZIP"));
}
/**
* @see DATAREST-348
*/
@Test
public void appliesMergePatchCorrectly() throws Exception {
String input = "{ \"address\" : { \"zipCode\" : \"ZIP\"}, \"lastname\" : null }";
User result = handler.applyMergePatch(asStream(input), user);
assertThat(result.lastname, is(nullValue()));
assertThat(result.address.zipCode, is("ZIP"));
}
/**
* DATAREST-537
*/
@Test
public void removesArrayItemCorrectly() throws Exception {
User thomas = new User();
thomas.firstname = "Thomas";
User christoph = new User();
christoph.firstname = "Christoph";
this.user.colleagues = Arrays.asList(thomas, christoph);
String input = "[{ \"op\": \"remove\", \"path\": \"/colleagues/0\" }]";
handler.applyPatch(asStream(input), user);
assertThat(user.colleagues, hasSize(1));
assertThat(user.colleagues.get(0).firstname, is(christoph.firstname));
}
/**
* @see DATAREST-609
*/
@Test
public void hintsToMediaTypeIfBodyCantBeRead() throws Exception {
exception.expect(HttpMessageNotReadableException.class);
exception.expectMessage(RestMediaTypes.JSON_PATCH_JSON.toString());
handler.applyPatch(asStream("{ \"foo\" : \"bar\" }"), new User());
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2013-2016 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.config;
import static org.hamcrest.CoreMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.tests.mongodb.MongoDbRepositoryConfig;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
/**
* Integration tests to check the legacy representation is rendered if HAL is not the default media type.
*
* @author Oliver Gierke
*/
@ContextConfiguration
public class LegacyRepresentationConfigIntegrationTests extends AbstractRepositoryRestMvcConfigurationIntegrationTests {
@Configuration
@Import({ MongoDbRepositoryConfig.class, RepositoryRestMvcConfiguration.class })
static class Config extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}
/**
* @see DATAREST-213, DATAREST-617
*/
@Test
public void returnsJsonIfConfiguredAndRequested() throws Exception {
for (String resource : Arrays.asList("/", "/users")) {
mvc.perform(get(resource).accept(MediaType.APPLICATION_JSON)). //
andExpect(jsonPath("links", is(notNullValue())));
}
}
/**
* @see DATAREST-213, DATAREST-617
*/
@Test
public void returnsJsonIfConfigured() throws Exception {
for (String resource : Arrays.asList("/", "/users")) {
mvc.perform(get(resource).accept(MediaType.ALL)). //
andExpect(jsonPath("links", is(notNullValue())));
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2015-2016 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.config;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.QuerydslRepositoryInvokerAdapter;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.tests.mongodb.QUser;
import org.springframework.data.rest.tests.mongodb.Receipt;
import org.springframework.data.rest.tests.mongodb.ReceiptRepository;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Unit tests for {@link QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests {
static final Map<String, String[]> NO_PARAMETERS = Collections.emptyMap();
@Mock Repositories repositories;
@Mock RepositoryInvokerFactory invokerFactory;
@Mock ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
@Mock RepositoryInvoker invoker;
@Mock MethodParameter parameter;
QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver resolver;
@Before
public void setUp() {
QuerydslBindingsFactory factory = new QuerydslBindingsFactory(SimpleEntityPathResolver.INSTANCE);
ReflectionTestUtils.setField(factory, "repositories", repositories);
QuerydslPredicateBuilder builder = new QuerydslPredicateBuilder(new DefaultConversionService(),
factory.getEntityPathResolver());
this.resolver = new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(repositories, invokerFactory,
resourceMetadataResolver, builder, factory);
when(parameter.hasParameterAnnotation(QuerydslPredicate.class)).thenReturn(true);
}
/**
* @see DATAREST-616
*/
@Test
public void returnsInvokerIfRepositoryIsNotQuerydslAware() {
ReceiptRepository repository = mock(ReceiptRepository.class);
when(repositories.getRepositoryFor(Receipt.class)).thenReturn(repository);
RepositoryInvoker result = resolver.postProcess(parameter, invoker, Receipt.class, NO_PARAMETERS);
assertThat(result, is(invoker));
}
/**
* @see DATAREST-616
*/
@Test
public void wrapsInvokerInQuerydslAdapter() {
Object repository = mock(QuerydslUserRepository.class);
when(repositories.getRepositoryFor(User.class)).thenReturn(repository);
RepositoryInvoker result = resolver.postProcess(parameter, invoker, User.class, NO_PARAMETERS);
assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class)));
}
/**
* @see DATAREST-616
*/
@Test
public void invokesCustomizationOnRepositoryIfItImplementsCustomizer() {
QuerydslCustomizingUserRepository repository = mock(QuerydslCustomizingUserRepository.class);
when(repositories.hasRepositoryFor(User.class)).thenReturn(true);
when(repositories.getRepositoryFor(User.class)).thenReturn(repository);
RepositoryInvoker result = resolver.postProcess(parameter, invoker, User.class, NO_PARAMETERS);
assertThat(result, is(instanceOf(QuerydslRepositoryInvokerAdapter.class)));
verify(repository, times(1)).customize(Mockito.any(QuerydslBindings.class), Mockito.any(QUser.class));
}
interface QuerydslUserRepository extends QueryDslPredicateExecutor<User> {}
interface QuerydslCustomizingUserRepository
extends QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2012-2016 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.json;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.tests.RepositoryTestsConfig;
import org.springframework.data.rest.tests.mongodb.Address;
import org.springframework.data.rest.tests.mongodb.MongoDbRepositoryConfig;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.data.rest.tests.mongodb.User.Gender;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.hal.HalLinkDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for entity (de)serialization.
*
* @author Jon Brisbin
* @author Greg Turnquist
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MongoDbRepositoryConfig.class, RepositoryTestsConfig.class,
PersistentEntitySerializationTests.TestConfig.class })
@Transactional
public class PersistentEntitySerializationTests {
@Autowired ObjectMapper mapper;
@Autowired Repositories repositories;
@Configuration
static class TestConfig extends RepositoryTestsConfig {
@Bean
@Override
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = super.objectMapper();
objectMapper.registerModule(
new JacksonSerializers(new EnumTranslator(new MessageSourceAccessor(new StaticMessageSource()))));
return objectMapper;
}
}
LinkDiscoverer linkDiscoverer;
ProjectionFactory projectionFactory;
@Before
public void setUp() {
RequestContextHolder.setRequestAttributes(new ServletWebRequest(new MockHttpServletRequest()));
this.linkDiscoverer = new HalLinkDiscoverer();
this.projectionFactory = new SpelAwareProxyProjectionFactory();
}
/**
* @see DATAREST-250
*/
@Test
public void serializesEmbeddedReferencesCorrectly() throws Exception {
User user = new User();
user.address = new Address();
user.address.street = "Street";
PersistentEntityResource userResource = PersistentEntityResource.//
build(user, repositories.getPersistentEntity(User.class)).//
withLink(new Link("/users/1")).//
build();
PagedResources<PersistentEntityResource> persistentEntityResource = new PagedResources<PersistentEntityResource>(
Arrays.asList(userResource), new PageMetadata(1, 0, 10));
String result = mapper.writeValueAsString(persistentEntityResource);
assertThat(JsonPath.read(result, "$_embedded.users[*].address"), is(notNullValue()));
}
/**
* @see DATAREST-654
*/
@Test
public void deserializesTranslatedEnumProperty() throws Exception {
assertThat(mapper.readValue("{ \"gender\" : \"Male\" }", User.class).gender, is(Gender.MALE));
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright 2014-2016 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.json;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.config.JsonSchemaFormat;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.tests.TestMvcClient;
import org.springframework.data.rest.tests.mongodb.MongoDbRepositoryConfig;
import org.springframework.data.rest.tests.mongodb.Profile;
import org.springframework.data.rest.tests.mongodb.User;
import org.springframework.data.rest.tests.mongodb.User.EmailAddress;
import org.springframework.data.rest.tests.mongodb.User.TypeWithPattern;
import org.springframework.data.rest.tests.mongodb.groovy.SimulatedGroovyDomainClass;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter.ValueTypeSchemaPropertyCustomizerFactory;
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverterUnitTests.TestConfiguration;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* @author Oliver Gierke
* @author Greg Turnquist
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MongoDbRepositoryConfig.class, TestConfiguration.class })
public class PersistentEntityToJsonSchemaConverterUnitTests {
@Autowired @Qualifier("resourceDescriptionMessageSourceAccessor") MessageSourceAccessor accessor;
@Autowired RepositoryRestConfiguration configuration;
@Autowired PersistentEntities entities;
@Autowired @Qualifier("objectMapper") ObjectMapper objectMapper;
@Autowired Associations associations;
@Configuration
@Import(RepositoryRestMvcConfiguration.class)
static class TestConfiguration extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getMetadataConfiguration().registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class);
config.getMetadataConfiguration().registerFormattingPatternFor("[A-Z]+", TypeWithPattern.class);
config.exposeIdsFor(Profile.class);
}
}
PersistentEntityToJsonSchemaConverter converter;
@Before
public void setUp() {
TestMvcClient.initWebTest();
ValueTypeSchemaPropertyCustomizerFactory customizerFactory = mock(ValueTypeSchemaPropertyCustomizerFactory.class);
converter = new PersistentEntityToJsonSchemaConverter(entities, associations, accessor, objectMapper, configuration,
customizerFactory);
}
/**
* @see DATAREST-631, DATAREST-632
*/
@Test
public void fulfillsConstraintsForProfile() {
List<Constraint> constraints = new ArrayList<Constraint>();
constraints.add(new Constraint("$.properties.id", is(notNullValue()), "Has descriptor for id property"));
constraints.add(new Constraint("$.description", is("Profile description"), "Adds description to schema root"));
constraints.add(new Constraint("$.properties.renamed", is(notNullValue()), "Has descriptor for renamed property"));
constraints.add(
new Constraint("$.properties.aliased", is(nullValue()), "No descriptor for original name of renamed property"));
assertConstraints(Profile.class, constraints);
}
/**
* @see DATAREST-632
*/
@Test
public void fulfillsConstraintsForUser() throws Exception {
List<Constraint> constraints = new ArrayList<Constraint>();
constraints.add(new Constraint("$.properties.id", is(nullValue()), "Does NOT have descriptor for id property"));
constraints.add(new Constraint("$.properties.firstname.type", is("string"), "Exposes firstname as String"));
constraints
.add(new Constraint("$.definitions.address", is(notNullValue()), "Exposes nested objects as definitions."));
constraints.add(new Constraint("$.definitions.address.type", is("object"), "Nested entity is of type 'object'"));
constraints.add(
new Constraint("$.definitions.address.properties.zipCode", is(notNullValue()), "Exposes nested properties"));
constraints.add(
new Constraint("$.definitions.address.requiredProperties[0]", is("zipCode"), "Lists nested required property"));
constraints.add(new Constraint("$.properties.gender.type", is("string"), "Enums are strings."));
constraints.add(new Constraint("$.properties.gender.enum", is(notNullValue()), "Exposes enum values."));
constraints
.add(new Constraint("$.properties.jodaDateTime.format", is("date-time"), "Exposes JodaTime dates in format."));
constraints
.add(new Constraint("$.properties.java8DateTime.format", is("date-time"), "Exposes Java 8 dates in format."));
constraints.add(new Constraint("$.properties.nicknames.type", is("array"), "Exposes collection of simple types."));
constraints.add(new Constraint("$.properties.nicknames.items.type", is("string"),
"Exposes element type of collection of simple types."));
constraints.add(new Constraint("$.properties.email.format", is("email"), "Uses manually configured format."));
constraints.add(new Constraint("$.properties.email.type", is("string"), "Treats types with format as String."));
constraints.add(
new Constraint("$.properties.shippingAddresses.type", is("array"), "Exposes collection of complex types."));
constraints
.add(new Constraint("$.properties.shippingAddresses.uniqueItems", is(true), "Exposes uniqueness for Sets."));
constraints.add(new Constraint("$.properties.shippingAddresses.items['$ref']", is("#/definitions/address"),
"References definition of complex element type."));
// DATAREST-531
constraints.add(new Constraint("$.properties.email.readOnly", is(true), "Email is read-only property"));
// DATAREST-644
constraints.add(new Constraint("$.properties.shippingAddresses.title", is("Shipping addresses"),
"Defaults titles correctly (split at camel case)"));
// DATAREST-665
constraints.add(new Constraint("$.properties.address.title", is("Adresse"), "I18n from simple property"));
constraints.add(new Constraint("$.properties.gender.title", is("Geschlecht"), "I18n from property on local type"));
constraints.add(
new Constraint("$.properties.firstname.title", is("Vorname"), "I18n from property on fully-qualified type"));
// DATAREST-690
constraints.add(new Constraint("$.properties.colleagues.items", is(nullValue()),
"Items must not appear for collection associations."));
assertConstraints(User.class, constraints);
}
/**
* @see DATAREST-754
*/
@Test
public void handlesGroovyDomainObjects() {
List<Constraint> constraints = new ArrayList<Constraint>();
constraints.add(new Constraint("$.properties.name", is(notNullValue()), "Has descriptor for name property"));
assertConstraints(SimulatedGroovyDomainClass.class, constraints);
}
@SuppressWarnings("unchecked")
private void assertConstraints(Class<?> type, Iterable<Constraint> constraints) {
String writeSchemaFor = writeSchemaFor(type);
for (Constraint constraint : constraints) {
try {
assertThat(constraint.description, JsonPath.read(writeSchemaFor, constraint.selector), constraint.matcher);
} catch (RuntimeException e) {
assertThat(e, constraint.matcher);
}
}
}
private String writeSchemaFor(Class<?> type) {
try {
return objectMapper.writeValueAsString(converter.convert(type));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("rawtypes")
private static class Constraint {
String selector;
Matcher matcher;
String description;
public Constraint(String selector, Matcher matcher, String description) {
this.selector = selector;
this.matcher = matcher;
this.description = description;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2014-2016 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.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.tests.TestMvcClient;
import org.springframework.data.rest.tests.mongodb.Profile;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.hateoas.Link;
/**
* Unit tests for {@link RepositoryLinkBuilder}.
*
* @author Oliver Gierke
*/
public class RepositoryLinkBuildUnitTests {
MongoMappingContext context = new MongoMappingContext();
/**
* @see DATAREST-292
*/
@Test
public void usesCurrentRequestsUriBaseForRelativeBaseUri() {
TestMvcClient.initWebTest();
assertRootUriFor("api", "http://localhost/api/profile");
}
/**
* @see DATAREST-292, DATAREST-296
*/
@Test
public void usesBaseUriOnlyIfItIsAbsolute() {
assertRootUriFor("http://foobar/api", "http://foobar/api/profile");
}
private void assertRootUriFor(String baseUri, String expectedUri) {
context.getPersistentEntity(Profile.class);
ResourceMappings mappings = new PersistentEntitiesResourceMappings(new PersistentEntities(Arrays.asList(context)));
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(mappings.getMetadataFor(Profile.class),
new BaseUri(baseUri));
Link link = builder.withSelfRel();
assertThat(link.getHref(), is(expectedUri));
}
}

View File

@@ -0,0 +1,14 @@
rest.description.profile=Profile description
# User
# Local property
address._title=Adresse
# Property on simple type
User.gender._title=Geschlecht
# Property on fully-qualified property
org.springframework.data.rest.tests.mongodb.User.firstname._title=Vorname
# Local property that should be trumped by the more precise definition above
firstname._title=emanroV

View File

@@ -0,0 +1,67 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data REST Tests - Security</name>
<artifactId>spring-data-rest-tests-security</artifactId>
<properties>
<spring-security.version>4.0.1.RELEASE</spring-security.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-tests-core</artifactId>
<version>2.5.0.BUILD-SNAPSHOT</version>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue</artifactId>
<version>${springdata.keyvalue}</version>
<scope>test</scope>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${spring-security.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Some files were not shown because too many files have changed in this diff Show More