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,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"
}