moved Rest Pagination article code from spring-rest-full to spring-boot-rest
This commit is contained in:
		
							parent
							
								
									5d6640a87b
								
							
						
					
					
						commit
						14b65b327f
					
				| @ -2,3 +2,4 @@ Module for the articles that are part of the Spring REST E-book: | ||||
| 
 | ||||
| 1. [Bootstrap a Web Application with Spring 5](https://www.baeldung.com/bootstraping-a-web-application-with-spring-and-java-based-configuration) | ||||
| 2. [Error Handling for REST with Spring](http://www.baeldung.com/exception-handling-for-rest-with-spring) | ||||
| 3. [REST Pagination in Spring](http://www.baeldung.com/rest-api-pagination-in-spring) | ||||
| @ -1,5 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <groupId>com.baeldung.web</groupId> | ||||
| @ -32,6 +33,30 @@ | ||||
|             <groupId>org.springframework</groupId> | ||||
|             <artifactId>spring-jdbc</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.data</groupId> | ||||
|             <artifactId>spring-data-jpa</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.h2database</groupId> | ||||
|             <artifactId>h2</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework</groupId> | ||||
|             <artifactId>spring-tx</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.data</groupId> | ||||
|             <artifactId>spring-data-commons</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- util --> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>com.google.guava</groupId> | ||||
|             <artifactId>guava</artifactId> | ||||
|             <version>${guava.version}</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
| @ -58,5 +83,6 @@ | ||||
|     <properties> | ||||
|         <start-class>com.baeldung.SpringBootRestApplication</start-class> | ||||
|         <htmlunit.version>2.32</htmlunit.version> | ||||
|         <guava.version>27.0.1-jre</guava.version> | ||||
|     </properties> | ||||
| </project> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.baeldung.web; | ||||
| package com.baeldung; | ||||
| 
 | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| @ -0,0 +1,16 @@ | ||||
| package com.baeldung.persistence; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import org.springframework.data.domain.Page; | ||||
| 
 | ||||
| public interface IOperations<T extends Serializable> { | ||||
| 
 | ||||
|     // read - all | ||||
| 
 | ||||
|     Page<T> findPaginated(int page, int size); | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     T create(final T entity); | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| package com.baeldung.persistence.dao; | ||||
| 
 | ||||
| import org.springframework.data.jpa.repository.JpaRepository; | ||||
| 
 | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| 
 | ||||
| public interface IFooDao extends JpaRepository<Foo, Long> { | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,83 @@ | ||||
| package com.baeldung.persistence.model; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import javax.persistence.Column; | ||||
| import javax.persistence.Entity; | ||||
| import javax.persistence.GeneratedValue; | ||||
| import javax.persistence.GenerationType; | ||||
| import javax.persistence.Id; | ||||
| 
 | ||||
| @Entity | ||||
| public class Foo implements Serializable { | ||||
| 
 | ||||
|     @Id | ||||
|     @GeneratedValue(strategy = GenerationType.AUTO) | ||||
|     private long id; | ||||
| 
 | ||||
|     @Column(nullable = false) | ||||
|     private String name; | ||||
| 
 | ||||
|     public Foo() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     public Foo(final String name) { | ||||
|         super(); | ||||
| 
 | ||||
|         this.name = name; | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     public long getId() { | ||||
|         return id; | ||||
|     } | ||||
| 
 | ||||
|     public void setId(final long id) { | ||||
|         this.id = id; | ||||
|     } | ||||
| 
 | ||||
|     public String getName() { | ||||
|         return name; | ||||
|     } | ||||
| 
 | ||||
|     public void setName(final String name) { | ||||
|         this.name = name; | ||||
|     } | ||||
| 
 | ||||
|     // | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         final int prime = 31; | ||||
|         int result = 1; | ||||
|         result = prime * result + ((name == null) ? 0 : name.hashCode()); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(final Object obj) { | ||||
|         if (this == obj) | ||||
|             return true; | ||||
|         if (obj == null) | ||||
|             return false; | ||||
|         if (getClass() != obj.getClass()) | ||||
|             return false; | ||||
|         final Foo other = (Foo) obj; | ||||
|         if (name == null) { | ||||
|             if (other.name != null) | ||||
|                 return false; | ||||
|         } else if (!name.equals(other.name)) | ||||
|             return false; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         final StringBuilder builder = new StringBuilder(); | ||||
|         builder.append("Foo [name=").append(name).append("]"); | ||||
|         return builder.toString(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.baeldung.persistence.service; | ||||
| 
 | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| 
 | ||||
| import com.baeldung.persistence.IOperations; | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| 
 | ||||
| public interface IFooService extends IOperations<Foo> { | ||||
|      | ||||
|     Page<Foo> findPaginated(Pageable pageable); | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.baeldung.persistence.service.common; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | ||||
| import org.springframework.data.repository.PagingAndSortingRepository; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| 
 | ||||
| import com.baeldung.persistence.IOperations; | ||||
| 
 | ||||
| @Transactional | ||||
| public abstract class AbstractService<T extends Serializable> implements IOperations<T> { | ||||
| 
 | ||||
|     // read - all | ||||
| 
 | ||||
|     @Override | ||||
|     public Page<T> findPaginated(final int page, final int size) { | ||||
|         return getDao().findAll(PageRequest.of(page, size)); | ||||
|     } | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     @Override | ||||
|     public T create(final T entity) { | ||||
|         return getDao().save(entity); | ||||
|     } | ||||
| 
 | ||||
|     protected abstract PagingAndSortingRepository<T, Long> getDao(); | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package com.baeldung.persistence.service.impl; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| import org.springframework.data.repository.PagingAndSortingRepository; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| 
 | ||||
| import com.baeldung.persistence.dao.IFooDao; | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| import com.baeldung.persistence.service.IFooService; | ||||
| import com.baeldung.persistence.service.common.AbstractService; | ||||
| 
 | ||||
| @Service | ||||
| @Transactional | ||||
| public class FooService extends AbstractService<Foo> implements IFooService { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private IFooDao dao; | ||||
| 
 | ||||
|     public FooService() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     @Override | ||||
|     protected PagingAndSortingRepository<Foo, Long> getDao() { | ||||
|         return dao; | ||||
|     } | ||||
| 
 | ||||
|     // custom methods | ||||
| 
 | ||||
|     @Override | ||||
|     public Page<Foo> findPaginated(Pageable pageable) { | ||||
|         return dao.findAll(pageable); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| package com.baeldung.spring; | ||||
| 
 | ||||
| import java.util.Properties; | ||||
| 
 | ||||
| import javax.sql.DataSource; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.PropertySource; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; | ||||
| import org.springframework.data.jpa.repository.config.EnableJpaRepositories; | ||||
| import org.springframework.jdbc.datasource.DriverManagerDataSource; | ||||
| import org.springframework.orm.jpa.JpaTransactionManager; | ||||
| import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; | ||||
| import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; | ||||
| import org.springframework.transaction.PlatformTransactionManager; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
| 
 | ||||
| import com.google.common.base.Preconditions; | ||||
| 
 | ||||
| @Configuration | ||||
| @EnableTransactionManagement | ||||
| @PropertySource({ "classpath:persistence-${envTarget:h2}.properties" }) | ||||
| @ComponentScan({ "com.baeldung.persistence" }) | ||||
| // @ImportResource("classpath*:springDataPersistenceConfig.xml") | ||||
| @EnableJpaRepositories(basePackages = "com.baeldung.persistence.dao") | ||||
| public class PersistenceConfig { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private Environment env; | ||||
| 
 | ||||
|     public PersistenceConfig() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public LocalContainerEntityManagerFactoryBean entityManagerFactory() { | ||||
|         final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); | ||||
|         em.setDataSource(dataSource()); | ||||
|         em.setPackagesToScan(new String[] { "com.baeldung.persistence.model" }); | ||||
| 
 | ||||
|         final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); | ||||
|         // vendorAdapter.set | ||||
|         em.setJpaVendorAdapter(vendorAdapter); | ||||
|         em.setJpaProperties(additionalProperties()); | ||||
| 
 | ||||
|         return em; | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public DataSource dataSource() { | ||||
|         final DriverManagerDataSource dataSource = new DriverManagerDataSource(); | ||||
|         dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName"))); | ||||
|         dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("jdbc.url"))); | ||||
|         dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user"))); | ||||
|         dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass"))); | ||||
| 
 | ||||
|         return dataSource; | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public PlatformTransactionManager transactionManager() { | ||||
|         final JpaTransactionManager transactionManager = new JpaTransactionManager(); | ||||
|         transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); | ||||
| 
 | ||||
|         return transactionManager; | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { | ||||
|         return new PersistenceExceptionTranslationPostProcessor(); | ||||
|     } | ||||
| 
 | ||||
|     final Properties additionalProperties() { | ||||
|         final Properties hibernateProperties = new Properties(); | ||||
|         hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); | ||||
|         hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect")); | ||||
|         // hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", "true"); | ||||
|         return hibernateProperties; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.baeldung.spring; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.web.servlet.ViewResolver; | ||||
| import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; | ||||
| import org.springframework.web.servlet.config.annotation.EnableWebMvc; | ||||
| import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| import org.springframework.web.servlet.view.InternalResourceViewResolver; | ||||
| 
 | ||||
| @Configuration | ||||
| @ComponentScan("com.baeldung.web") | ||||
| @EnableWebMvc | ||||
| public class WebConfig implements WebMvcConfigurer { | ||||
| 
 | ||||
|     public WebConfig() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public ViewResolver viewResolver() { | ||||
|         final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); | ||||
|         viewResolver.setPrefix("/WEB-INF/view/"); | ||||
|         viewResolver.setSuffix(".jsp"); | ||||
|         return viewResolver; | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
|     @Override | ||||
|     public void addViewControllers(final ViewControllerRegistry registry) { | ||||
|         registry.addViewController("/graph.html"); | ||||
|         registry.addViewController("/homepage.html"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { | ||||
|         configurer.defaultContentType(MediaType.APPLICATION_JSON); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -27,5 +27,4 @@ public class MyErrorController extends BasicErrorController { | ||||
|         HttpStatus status = getStatus(request); | ||||
|         return new ResponseEntity<>(body, status); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -1,8 +0,0 @@ | ||||
| package com.baeldung.web.config; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| @Configuration | ||||
| public class WebConfig { | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,89 @@ | ||||
| package com.baeldung.web.controller; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.ApplicationEventPublisher; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.ResponseBody; | ||||
| import org.springframework.web.bind.annotation.ResponseStatus; | ||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||
| 
 | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| import com.baeldung.persistence.service.IFooService; | ||||
| import com.baeldung.web.exception.MyResourceNotFoundException; | ||||
| import com.baeldung.web.hateoas.event.PaginatedResultsRetrievedEvent; | ||||
| import com.baeldung.web.hateoas.event.ResourceCreatedEvent; | ||||
| import com.google.common.base.Preconditions; | ||||
| 
 | ||||
| @Controller | ||||
| @RequestMapping(value = "/auth/foos") | ||||
| public class FooController { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private ApplicationEventPublisher eventPublisher; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private IFooService service; | ||||
| 
 | ||||
|     public FooController() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     // read - all | ||||
| 
 | ||||
|     @RequestMapping(params = { "page", "size" }, method = RequestMethod.GET) | ||||
|     @ResponseBody | ||||
|     public List<Foo> findPaginated(@RequestParam("page") final int page, @RequestParam("size") final int size, | ||||
|         final UriComponentsBuilder uriBuilder, final HttpServletResponse response) { | ||||
|         final Page<Foo> resultPage = service.findPaginated(page, size); | ||||
|         if (page > resultPage.getTotalPages()) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
|         eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, page, | ||||
|             resultPage.getTotalPages(), size)); | ||||
| 
 | ||||
|         return resultPage.getContent(); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pageable") | ||||
|     @ResponseBody | ||||
|     public List<Foo> findPaginatedWithPageable(Pageable pageable, final UriComponentsBuilder uriBuilder, | ||||
|         final HttpServletResponse response) { | ||||
|         final Page<Foo> resultPage = service.findPaginated(pageable); | ||||
|         if (pageable.getPageNumber() > resultPage.getTotalPages()) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
|         eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, | ||||
|             pageable.getPageNumber(), resultPage.getTotalPages(), pageable.getPageSize())); | ||||
| 
 | ||||
|         return resultPage.getContent(); | ||||
|     } | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     @RequestMapping(method = RequestMethod.POST) | ||||
|     @ResponseStatus(HttpStatus.CREATED) | ||||
|     @ResponseBody | ||||
|     public Foo create(@RequestBody final Foo resource, final HttpServletResponse response) { | ||||
|         Preconditions.checkNotNull(resource); | ||||
|         final Foo foo = service.create(resource); | ||||
|         final Long idOfCreatedResource = foo.getId(); | ||||
| 
 | ||||
|         eventPublisher.publishEvent(new ResourceCreatedEvent(this, response, idOfCreatedResource)); | ||||
| 
 | ||||
|         return foo; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package com.baeldung.web.controller; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.ResponseStatus; | ||||
| import org.springframework.web.util.UriTemplate; | ||||
| 
 | ||||
| import com.baeldung.web.util.LinkUtil; | ||||
| 
 | ||||
| @Controller | ||||
| @RequestMapping(value = "/auth/") | ||||
| public class RootController { | ||||
| 
 | ||||
|     public RootController() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     // discover | ||||
| 
 | ||||
|     @RequestMapping(value = "admin", method = RequestMethod.GET) | ||||
|     @ResponseStatus(value = HttpStatus.NO_CONTENT) | ||||
|     public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) { | ||||
|         final String rootUri = request.getRequestURL() | ||||
|             .toString(); | ||||
| 
 | ||||
|         final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo"); | ||||
|         final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection"); | ||||
|         response.addHeader("Link", linkToFoo); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package org.baeldung.web.hateoas.event; | ||||
| package com.baeldung.web.hateoas.event; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| @ -0,0 +1,28 @@ | ||||
| package com.baeldung.web.hateoas.event; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.context.ApplicationEvent; | ||||
| 
 | ||||
| public class ResourceCreatedEvent extends ApplicationEvent { | ||||
|     private final HttpServletResponse response; | ||||
|     private final long idOfNewResource; | ||||
| 
 | ||||
|     public ResourceCreatedEvent(final Object source, final HttpServletResponse response, final long idOfNewResource) { | ||||
|         super(source); | ||||
| 
 | ||||
|         this.response = response; | ||||
|         this.idOfNewResource = idOfNewResource; | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     public HttpServletResponse getResponse() { | ||||
|         return response; | ||||
|     } | ||||
| 
 | ||||
|     public long getIdOfNewResource() { | ||||
|         return idOfNewResource; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,13 +1,13 @@ | ||||
| package org.baeldung.web.hateoas.listener; | ||||
| package com.baeldung.web.hateoas.listener; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.baeldung.web.hateoas.event.PaginatedResultsRetrievedEvent; | ||||
| import org.baeldung.web.util.LinkUtil; | ||||
| import org.springframework.context.ApplicationListener; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||
| 
 | ||||
| import com.baeldung.web.hateoas.event.PaginatedResultsRetrievedEvent; | ||||
| import com.baeldung.web.util.LinkUtil; | ||||
| import com.google.common.base.Preconditions; | ||||
| import com.google.common.net.HttpHeaders; | ||||
| 
 | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.baeldung.web.hateoas.listener; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.apache.http.HttpHeaders; | ||||
| import com.baeldung.web.hateoas.event.ResourceCreatedEvent; | ||||
| import org.springframework.context.ApplicationListener; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.servlet.support.ServletUriComponentsBuilder; | ||||
| 
 | ||||
| import com.google.common.base.Preconditions; | ||||
| 
 | ||||
| @Component | ||||
| class ResourceCreatedDiscoverabilityListener implements ApplicationListener<ResourceCreatedEvent> { | ||||
| 
 | ||||
|     @Override | ||||
|     public void onApplicationEvent(final ResourceCreatedEvent resourceCreatedEvent) { | ||||
|         Preconditions.checkNotNull(resourceCreatedEvent); | ||||
| 
 | ||||
|         final HttpServletResponse response = resourceCreatedEvent.getResponse(); | ||||
|         final long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); | ||||
| 
 | ||||
|         addLinkHeaderOnResourceCreation(response, idOfNewResource); | ||||
|     } | ||||
| 
 | ||||
|     void addLinkHeaderOnResourceCreation(final HttpServletResponse response, final long idOfNewResource) { | ||||
|         // final String requestUrl = request.getRequestURL().toString(); | ||||
|         // final URI uri = new UriTemplate("{requestUrl}/{idOfNewResource}").expand(requestUrl, idOfNewResource); | ||||
| 
 | ||||
|         final URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri(); | ||||
|         response.setHeader(HttpHeaders.LOCATION, uri.toASCIIString()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.baeldung.web.util; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| /** | ||||
|  * Provides some constants and utility methods to build a Link Header to be stored in the {@link HttpServletResponse} object | ||||
|  */ | ||||
| public final class LinkUtil { | ||||
| 
 | ||||
|     public static final String REL_COLLECTION = "collection"; | ||||
|     public static final String REL_NEXT = "next"; | ||||
|     public static final String REL_PREV = "prev"; | ||||
|     public static final String REL_FIRST = "first"; | ||||
|     public static final String REL_LAST = "last"; | ||||
| 
 | ||||
|     private LinkUtil() { | ||||
|         throw new AssertionError(); | ||||
|     } | ||||
| 
 | ||||
|     // | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a Link Header to be stored in the {@link HttpServletResponse} to provide Discoverability features to the user | ||||
|      *  | ||||
|      * @param uri | ||||
|      *            the base uri | ||||
|      * @param rel | ||||
|      *            the relative path | ||||
|      *  | ||||
|      * @return the complete url | ||||
|      */ | ||||
|     public static String createLinkHeader(final String uri, final String rel) { | ||||
|         return "<" + uri + ">; rel=\"" + rel + "\""; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| package com.baeldung.web.util; | ||||
| 
 | ||||
| import org.springframework.http.HttpStatus; | ||||
| 
 | ||||
| import com.baeldung.web.exception.MyResourceNotFoundException; | ||||
| 
 | ||||
| /** | ||||
|  * Simple static methods to be called at the start of your own methods to verify correct arguments and state. If the Precondition fails, an {@link HttpStatus} code is thrown | ||||
|  */ | ||||
| public final class RestPreconditions { | ||||
| 
 | ||||
|     private RestPreconditions() { | ||||
|         throw new AssertionError(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     /** | ||||
|      * Check if some value was found, otherwise throw exception. | ||||
|      *  | ||||
|      * @param expression | ||||
|      *            has value true if found, otherwise false | ||||
|      * @throws MyResourceNotFoundException | ||||
|      *             if expression is false, means value not found. | ||||
|      */ | ||||
|     public static void checkFound(final boolean expression) { | ||||
|         if (!expression) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if some value was found, otherwise throw exception. | ||||
|      *  | ||||
|      * @param expression | ||||
|      *            has value true if found, otherwise false | ||||
|      * @throws MyResourceNotFoundException | ||||
|      *             if expression is false, means value not found. | ||||
|      */ | ||||
|     public static <T> T checkFound(final T resource) { | ||||
|         if (resource == null) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
| 
 | ||||
|         return resource; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,3 +1,6 @@ | ||||
| server.port=8082 | ||||
| server.servlet.context-path=/spring-boot-rest | ||||
| 
 | ||||
| ### Spring Boot default error handling configurations | ||||
| #server.error.whitelabel.enabled=false | ||||
| #server.error.include-stacktrace=always | ||||
| @ -0,0 +1,22 @@ | ||||
| ## jdbc.X | ||||
| #jdbc.driverClassName=com.mysql.jdbc.Driver | ||||
| #jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true | ||||
| #jdbc.user=tutorialuser | ||||
| #jdbc.pass=tutorialmy5ql | ||||
| # | ||||
| ## hibernate.X | ||||
| #hibernate.dialect=org.hibernate.dialect.MySQL5Dialect | ||||
| #hibernate.show_sql=false | ||||
| #hibernate.hbm2ddl.auto=create-drop | ||||
| 
 | ||||
| 
 | ||||
| # jdbc.X | ||||
| jdbc.driverClassName=org.h2.Driver | ||||
| jdbc.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE | ||||
| jdbc.user=sa | ||||
| jdbc.pass= | ||||
| 
 | ||||
| # hibernate.X | ||||
| hibernate.dialect=org.hibernate.dialect.H2Dialect | ||||
| hibernate.show_sql=false | ||||
| hibernate.hbm2ddl.auto=create-drop | ||||
| @ -0,0 +1,10 @@ | ||||
| # jdbc.X | ||||
| jdbc.driverClassName=com.mysql.jdbc.Driver | ||||
| jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true | ||||
| jdbc.user=tutorialuser | ||||
| jdbc.pass=tutorialmy5ql | ||||
| 
 | ||||
| # hibernate.X | ||||
| hibernate.dialect=org.hibernate.dialect.MySQL5Dialect | ||||
| hibernate.show_sql=false | ||||
| hibernate.hbm2ddl.auto=create-drop | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" | ||||
|     xsi:schemaLocation=" | ||||
|     http://www.springframework.org/schema/beans  | ||||
|     http://www.springframework.org/schema/beans/spring-beans.xsd | ||||
|     http://www.springframework.org/schema/data/jpa  | ||||
|     http://www.springframework.org/schema/data/jpa/spring-jpa.xsd" | ||||
| > | ||||
| 
 | ||||
|     <jpa:repositories base-package="com.baeldung.persistence.dao"/> | ||||
| 
 | ||||
| </beans> | ||||
							
								
								
									
										5
									
								
								spring-boot-rest/src/test/java/com/baeldung/Consts.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spring-boot-rest/src/test/java/com/baeldung/Consts.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| package com.baeldung; | ||||
| 
 | ||||
| public interface Consts { | ||||
|     int APPLICATION_PORT = 8082; | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.baeldung.web; | ||||
| package com.baeldung; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| @ -0,0 +1,103 @@ | ||||
| package com.baeldung.common.web; | ||||
| 
 | ||||
| import static com.baeldung.web.util.HTTPLinkHeaderUtil.extractURIByRel; | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomNumeric; | ||||
| import static org.hamcrest.Matchers.is; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertThat; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import com.google.common.net.HttpHeaders; | ||||
| 
 | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| public abstract class AbstractBasicLiveTest<T extends Serializable> extends AbstractLiveTest<T> { | ||||
| 
 | ||||
|     public AbstractBasicLiveTest(final Class<T> clazzToSet) { | ||||
|         super(clazzToSet); | ||||
|     } | ||||
| 
 | ||||
|     // find - all - paginated | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenResourcesAreRetrievedPaged_then200IsReceived() { | ||||
|         create(); | ||||
|          | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=10"); | ||||
| 
 | ||||
|         assertThat(response.getStatusCode(), is(200)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived() { | ||||
|         final String url = getURL() + "?page=" + randomNumeric(5) + "&size=10"; | ||||
|         final Response response = RestAssured.get(url); | ||||
| 
 | ||||
|         assertThat(response.getStatusCode(), is(404)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources() { | ||||
|         create(); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=10"); | ||||
| 
 | ||||
|         assertFalse(response.body().as(List.class).isEmpty()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext() { | ||||
|         create(); | ||||
|         create(); | ||||
|         create(); | ||||
|          | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
| 
 | ||||
|         final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next"); | ||||
|         assertEquals(getURL() + "?page=1&size=2", uriToNextPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage() { | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
| 
 | ||||
|         final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev"); | ||||
|         assertNull(uriToPrevPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious() { | ||||
|         create(); | ||||
|         create(); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(getURL() + "?page=1&size=2"); | ||||
| 
 | ||||
|         final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev"); | ||||
|         assertEquals(getURL() + "?page=0&size=2", uriToPrevPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable() { | ||||
|         create(); | ||||
|         create(); | ||||
|         create(); | ||||
|          | ||||
|         final Response first = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
|         final String uriToLastPage = extractURIByRel(first.getHeader(HttpHeaders.LINK), "last"); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(uriToLastPage); | ||||
| 
 | ||||
|         final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next"); | ||||
|         assertNull(uriToNextPage); | ||||
|     } | ||||
| 
 | ||||
|     // count | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,65 @@ | ||||
| package com.baeldung.common.web; | ||||
| 
 | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| import static com.baeldung.Consts.APPLICATION_PORT; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| import com.baeldung.test.IMarshaller; | ||||
| import com.google.common.base.Preconditions; | ||||
| import com.google.common.net.HttpHeaders; | ||||
| 
 | ||||
| public abstract class AbstractLiveTest<T extends Serializable> { | ||||
| 
 | ||||
|     protected final Class<T> clazz; | ||||
| 
 | ||||
|     @Autowired | ||||
|     protected IMarshaller marshaller; | ||||
| 
 | ||||
|     public AbstractLiveTest(final Class<T> clazzToSet) { | ||||
|         super(); | ||||
| 
 | ||||
|         Preconditions.checkNotNull(clazzToSet); | ||||
|         clazz = clazzToSet; | ||||
|     } | ||||
| 
 | ||||
|     // template method | ||||
| 
 | ||||
|     public abstract void create(); | ||||
| 
 | ||||
|     public abstract String createAsUri(); | ||||
| 
 | ||||
|     protected final void create(final T resource) { | ||||
|         createAsUri(resource); | ||||
|     } | ||||
| 
 | ||||
|     protected final String createAsUri(final T resource) { | ||||
|         final Response response = createAsResponse(resource); | ||||
|         Preconditions.checkState(response.getStatusCode() == 201, "create operation: " + response.getStatusCode()); | ||||
| 
 | ||||
|         final String locationOfCreatedResource = response.getHeader(HttpHeaders.LOCATION); | ||||
|         Preconditions.checkNotNull(locationOfCreatedResource); | ||||
|         return locationOfCreatedResource; | ||||
|     } | ||||
| 
 | ||||
|     final Response createAsResponse(final T resource) { | ||||
|         Preconditions.checkNotNull(resource); | ||||
| 
 | ||||
|         final String resourceAsString = marshaller.encode(resource); | ||||
|         return RestAssured.given() | ||||
|             .contentType(marshaller.getMime()) | ||||
|             .body(resourceAsString) | ||||
|             .post(getURL()); | ||||
|     } | ||||
| 
 | ||||
|     // | ||||
| 
 | ||||
|     protected String getURL() { | ||||
|         return "http://localhost:" + APPLICATION_PORT + "/spring-boot-rest/auth/foos"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.baeldung.spring; | ||||
| 
 | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| 
 | ||||
| @Configuration | ||||
| @ComponentScan("com.baeldung.test") | ||||
| public class ConfigIntegrationTest implements WebMvcConfigurer { | ||||
| 
 | ||||
|     public ConfigIntegrationTest() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,15 @@ | ||||
| package com.baeldung.test; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public interface IMarshaller { | ||||
| 
 | ||||
|     <T> String encode(final T entity); | ||||
| 
 | ||||
|     <T> T decode(final String entityAsString, final Class<T> clazz); | ||||
| 
 | ||||
|     <T> List<T> decodeList(final String entitiesAsString, final Class<T> clazz); | ||||
| 
 | ||||
|     String getMime(); | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,81 @@ | ||||
| package com.baeldung.test; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.MediaType; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.type.TypeReference; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.google.common.base.Preconditions; | ||||
| 
 | ||||
| public final class JacksonMarshaller implements IMarshaller { | ||||
|     private final Logger logger = LoggerFactory.getLogger(JacksonMarshaller.class); | ||||
| 
 | ||||
|     private final ObjectMapper objectMapper; | ||||
| 
 | ||||
|     public JacksonMarshaller() { | ||||
|         super(); | ||||
| 
 | ||||
|         objectMapper = new ObjectMapper(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     @Override | ||||
|     public final <T> String encode(final T resource) { | ||||
|         Preconditions.checkNotNull(resource); | ||||
|         String entityAsJSON = null; | ||||
|         try { | ||||
|             entityAsJSON = objectMapper.writeValueAsString(resource); | ||||
|         } catch (final IOException ioEx) { | ||||
|             logger.error("", ioEx); | ||||
|         } | ||||
| 
 | ||||
|         return entityAsJSON; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final <T> T decode(final String resourceAsString, final Class<T> clazz) { | ||||
|         Preconditions.checkNotNull(resourceAsString); | ||||
| 
 | ||||
|         T entity = null; | ||||
|         try { | ||||
|             entity = objectMapper.readValue(resourceAsString, clazz); | ||||
|         } catch (final IOException ioEx) { | ||||
|             logger.error("", ioEx); | ||||
|         } | ||||
| 
 | ||||
|         return entity; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public final <T> List<T> decodeList(final String resourcesAsString, final Class<T> clazz) { | ||||
|         Preconditions.checkNotNull(resourcesAsString); | ||||
| 
 | ||||
|         List<T> entities = null; | ||||
|         try { | ||||
|             if (clazz.equals(Foo.class)) { | ||||
|                 entities = objectMapper.readValue(resourcesAsString, new TypeReference<List<Foo>>() { | ||||
|                     // ... | ||||
|                 }); | ||||
|             } else { | ||||
|                 entities = objectMapper.readValue(resourcesAsString, List.class); | ||||
|             } | ||||
|         } catch (final IOException ioEx) { | ||||
|             logger.error("", ioEx); | ||||
|         } | ||||
| 
 | ||||
|         return entities; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final String getMime() { | ||||
|         return MediaType.APPLICATION_JSON.toString(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,49 @@ | ||||
| package com.baeldung.test; | ||||
| 
 | ||||
| import org.springframework.beans.factory.FactoryBean; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Profile; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| @Component | ||||
| @Profile("test") | ||||
| public class TestMarshallerFactory implements FactoryBean<IMarshaller> { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private Environment env; | ||||
| 
 | ||||
|     public TestMarshallerFactory() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     @Override | ||||
|     public IMarshaller getObject() { | ||||
|         final String testMime = env.getProperty("test.mime"); | ||||
|         if (testMime != null) { | ||||
|             switch (testMime) { | ||||
|             case "json": | ||||
|                 return new JacksonMarshaller(); | ||||
|             case "xml": | ||||
|                 // If we need to implement xml marshaller we can include spring-rest-full XStreamMarshaller | ||||
|                 throw new IllegalStateException(); | ||||
|             default: | ||||
|                 throw new IllegalStateException(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return new JacksonMarshaller(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Class<IMarshaller> getObjectType() { | ||||
|         return IMarshaller.class; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isSingleton() { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.baeldung.web; | ||||
| 
 | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; | ||||
| 
 | ||||
| import org.junit.runner.RunWith; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.ContextConfiguration; | ||||
| import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; | ||||
| import org.springframework.test.context.support.AnnotationConfigContextLoader; | ||||
| 
 | ||||
| import com.baeldung.common.web.AbstractBasicLiveTest; | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| import com.baeldung.spring.ConfigIntegrationTest; | ||||
| 
 | ||||
| @RunWith(SpringJUnit4ClassRunner.class) | ||||
| @ContextConfiguration(classes = { ConfigIntegrationTest.class }, loader = AnnotationConfigContextLoader.class) | ||||
| @ActiveProfiles("test") | ||||
| public class FooLiveTest extends AbstractBasicLiveTest<Foo> { | ||||
| 
 | ||||
|     public FooLiveTest() { | ||||
|         super(Foo.class); | ||||
|     } | ||||
| 
 | ||||
|     // API | ||||
| 
 | ||||
|     @Override | ||||
|     public final void create() { | ||||
|         create(new Foo(randomAlphabetic(6))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final String createAsUri() { | ||||
|         return createAsUri(new Foo(randomAlphabetic(6))); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,19 +1,14 @@ | ||||
| package org.baeldung.web; | ||||
| package com.baeldung.web; | ||||
| 
 | ||||
| import static com.baeldung.Consts.APPLICATION_PORT; | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomNumeric; | ||||
| import static org.baeldung.Consts.APPLICATION_PORT; | ||||
| import static org.hamcrest.Matchers.is; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertThat; | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.baeldung.common.web.AbstractBasicLiveTest; | ||||
| import org.baeldung.persistence.model.Foo; | ||||
| import org.baeldung.spring.ConfigIntegrationTest; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| @ -21,6 +16,13 @@ import org.springframework.test.context.ContextConfiguration; | ||||
| import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; | ||||
| import org.springframework.test.context.support.AnnotationConfigContextLoader; | ||||
| 
 | ||||
| import com.baeldung.common.web.AbstractBasicLiveTest; | ||||
| import com.baeldung.persistence.model.Foo; | ||||
| import com.baeldung.spring.ConfigIntegrationTest; | ||||
| 
 | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| @RunWith(SpringJUnit4ClassRunner.class) | ||||
| @ContextConfiguration(classes = { ConfigIntegrationTest.class }, loader = AnnotationConfigContextLoader.class) | ||||
| @ActiveProfiles("test") | ||||
| @ -34,7 +36,7 @@ public class FooPageableLiveTest extends AbstractBasicLiveTest<Foo> { | ||||
| 
 | ||||
|     @Override | ||||
|     public final void create() { | ||||
|         create(new Foo(randomAlphabetic(6))); | ||||
|         super.create(new Foo(randomAlphabetic(6))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -45,6 +47,8 @@ public class FooPageableLiveTest extends AbstractBasicLiveTest<Foo> { | ||||
|     @Override | ||||
|     @Test | ||||
|     public void whenResourcesAreRetrievedPaged_then200IsReceived() { | ||||
|         this.create(); | ||||
|          | ||||
|         final Response response = RestAssured.get(getPageableURL() + "?page=0&size=10"); | ||||
| 
 | ||||
|         assertThat(response.getStatusCode(), is(200)); | ||||
| @ -70,7 +74,7 @@ public class FooPageableLiveTest extends AbstractBasicLiveTest<Foo> { | ||||
|     } | ||||
| 
 | ||||
|     protected String getPageableURL() { | ||||
|         return "http://localhost:" + APPLICATION_PORT + "/spring-rest-full/auth/foos/pageable"; | ||||
|         return "http://localhost:" + APPLICATION_PORT + "/spring-boot-rest/auth/foos/pageable"; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package com.baeldung.web; | ||||
| 
 | ||||
| import org.junit.runner.RunWith; | ||||
| import org.junit.runners.Suite; | ||||
| 
 | ||||
| @RunWith(Suite.class) | ||||
| @Suite.SuiteClasses({ | ||||
| // @formatter:off | ||||
|     FooLiveTest.class | ||||
|     ,FooPageableLiveTest.class | ||||
| }) // | ||||
| public class LiveTestSuiteLiveTest { | ||||
| 
 | ||||
| } | ||||
| @ -6,6 +6,7 @@ import static org.hamcrest.Matchers.hasKey; | ||||
| import static org.hamcrest.Matchers.is; | ||||
| import static org.hamcrest.Matchers.isA; | ||||
| import static org.hamcrest.Matchers.not; | ||||
| import static com.baeldung.Consts.APPLICATION_PORT; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| @ -16,8 +17,8 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; | ||||
| 
 | ||||
| public class ErrorHandlingLiveTest { | ||||
| 
 | ||||
|     private static final String BASE_URL = "http://localhost:8080"; | ||||
|     private static final String EXCEPTION_ENDPOINT = "/exception"; | ||||
|     private static final String BASE_URL = "http://localhost:" + APPLICATION_PORT + "/spring-boot-rest"; | ||||
|     private static final String EXCEPTION_ENDPOINT = BASE_URL + "/exception"; | ||||
| 
 | ||||
|     private static final String ERROR_RESPONSE_KEY_PATH = "error"; | ||||
|     private static final String XML_RESPONSE_KEY_PATH = "xmlkey"; | ||||
| @ -57,7 +58,7 @@ public class ErrorHandlingLiveTest { | ||||
|         try (WebClient webClient = new WebClient()) { | ||||
|             webClient.getOptions() | ||||
|                 .setThrowExceptionOnFailingStatusCode(false); | ||||
|             HtmlPage page = webClient.getPage(BASE_URL + EXCEPTION_ENDPOINT); | ||||
|             HtmlPage page = webClient.getPage(EXCEPTION_ENDPOINT); | ||||
|             assertThat(page.getBody() | ||||
|                 .asText()).contains("Whitelabel Error Page"); | ||||
|         } | ||||
|  | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.baeldung.web.util; | ||||
| 
 | ||||
| public final class HTTPLinkHeaderUtil { | ||||
| 
 | ||||
|     private HTTPLinkHeaderUtil() { | ||||
|         throw new AssertionError(); | ||||
|     } | ||||
| 
 | ||||
|     // | ||||
| 
 | ||||
|     public static String extractURIByRel(final String linkHeader, final String rel) { | ||||
|         if (linkHeader == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         String uriWithSpecifiedRel = null; | ||||
|         final String[] links = linkHeader.split(", "); | ||||
|         String linkRelation; | ||||
|         for (final String link : links) { | ||||
|             final int positionOfSeparator = link.indexOf(';'); | ||||
|             linkRelation = link.substring(positionOfSeparator + 1, link.length()).trim(); | ||||
|             if (extractTypeOfRelation(linkRelation).equals(rel)) { | ||||
|                 uriWithSpecifiedRel = link.substring(1, positionOfSeparator - 1); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return uriWithSpecifiedRel; | ||||
|     } | ||||
| 
 | ||||
|     private static Object extractTypeOfRelation(final String linkRelation) { | ||||
|         final int positionOfEquals = linkRelation.indexOf('='); | ||||
|         return linkRelation.substring(positionOfEquals + 2, linkRelation.length() - 1).trim(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -8,7 +8,6 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring | ||||
| The "Learn Spring Security" Classes: http://github.learnspringsecurity.com | ||||
| 
 | ||||
| ### Relevant Articles:  | ||||
| - [REST Pagination in Spring](http://www.baeldung.com/rest-api-pagination-in-spring) | ||||
| - [HATEOAS for a Spring REST Service](http://www.baeldung.com/rest-api-discoverability-with-spring) | ||||
| - [REST API Discoverability and HATEOAS](http://www.baeldung.com/restful-web-service-discoverability) | ||||
| - [ETags for REST with Spring](http://www.baeldung.com/etags-for-rest-with-spring) | ||||
|  | ||||
| @ -3,8 +3,6 @@ package org.baeldung.persistence; | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.springframework.data.domain.Page; | ||||
| 
 | ||||
| public interface IOperations<T extends Serializable> { | ||||
| 
 | ||||
|     // read - one | ||||
| @ -15,8 +13,6 @@ public interface IOperations<T extends Serializable> { | ||||
| 
 | ||||
|     List<T> findAll(); | ||||
| 
 | ||||
|     Page<T> findPaginated(int page, int size); | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     T create(final T entity); | ||||
|  | ||||
| @ -2,13 +2,9 @@ package org.baeldung.persistence.service; | ||||
| 
 | ||||
| import org.baeldung.persistence.IOperations; | ||||
| import org.baeldung.persistence.model.Foo; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| 
 | ||||
| public interface IFooService extends IOperations<Foo> { | ||||
| 
 | ||||
|     Foo retrieveByName(String name); | ||||
| 
 | ||||
|     Page<Foo> findPaginated(Pageable pageable); | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -4,8 +4,6 @@ import java.io.Serializable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.baeldung.persistence.IOperations; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.PageRequest; | ||||
| import org.springframework.data.repository.PagingAndSortingRepository; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| 
 | ||||
| @ -30,11 +28,6 @@ public abstract class AbstractService<T extends Serializable> implements IOperat | ||||
|         return Lists.newArrayList(getDao().findAll()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Page<T> findPaginated(final int page, final int size) { | ||||
|         return getDao().findAll(new PageRequest(page, size)); | ||||
|     } | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -7,8 +7,6 @@ import org.baeldung.persistence.model.Foo; | ||||
| import org.baeldung.persistence.service.IFooService; | ||||
| import org.baeldung.persistence.service.common.AbstractService; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| import org.springframework.data.repository.PagingAndSortingRepository; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| @ -48,9 +46,4 @@ public class FooService extends AbstractService<Foo> implements IFooService { | ||||
|         return Lists.newArrayList(getDao().findAll()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Page<Foo> findPaginated(Pageable pageable) { | ||||
|         return dao.findAll(pageable); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -6,27 +6,20 @@ import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.baeldung.persistence.model.Foo; | ||||
| import org.baeldung.persistence.service.IFooService; | ||||
| import org.baeldung.web.exception.MyResourceNotFoundException; | ||||
| import org.baeldung.web.hateoas.event.PaginatedResultsRetrievedEvent; | ||||
| import org.baeldung.web.hateoas.event.ResourceCreatedEvent; | ||||
| import org.baeldung.web.hateoas.event.SingleResourceRetrievedEvent; | ||||
| import org.baeldung.web.util.RestPreconditions; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.ApplicationEventPublisher; | ||||
| import org.springframework.data.domain.Page; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.ResponseBody; | ||||
| import org.springframework.web.bind.annotation.ResponseStatus; | ||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||
| 
 | ||||
| import com.google.common.base.Preconditions; | ||||
| 
 | ||||
| @ -72,30 +65,6 @@ public class FooController { | ||||
|         return service.findAll(); | ||||
|     } | ||||
| 
 | ||||
|     @RequestMapping(params = { "page", "size" }, method = RequestMethod.GET) | ||||
|     @ResponseBody | ||||
|     public List<Foo> findPaginated(@RequestParam("page") final int page, @RequestParam("size") final int size, final UriComponentsBuilder uriBuilder, final HttpServletResponse response) { | ||||
|         final Page<Foo> resultPage = service.findPaginated(page, size); | ||||
|         if (page > resultPage.getTotalPages()) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
|         eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size)); | ||||
| 
 | ||||
|         return resultPage.getContent(); | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/pageable") | ||||
|     @ResponseBody | ||||
|     public List<Foo> findPaginatedWithPageable(Pageable pageable, final UriComponentsBuilder uriBuilder, final HttpServletResponse response) { | ||||
|         final Page<Foo> resultPage = service.findPaginated(pageable); | ||||
|         if (pageable.getPageNumber() > resultPage.getTotalPages()) { | ||||
|             throw new MyResourceNotFoundException(); | ||||
|         } | ||||
|         eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent<Foo>(Foo.class, uriBuilder, response, pageable.getPageNumber(), resultPage.getTotalPages(), pageable.getPageSize())); | ||||
| 
 | ||||
|         return resultPage.getContent(); | ||||
|     } | ||||
| 
 | ||||
|     // write | ||||
| 
 | ||||
|     @RequestMapping(method = RequestMethod.POST) | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| package org.baeldung.web.util; | ||||
| 
 | ||||
| import org.baeldung.web.exception.MyResourceNotFoundException; | ||||
| import org.springframework.http.HttpStatus; | ||||
| 
 | ||||
| import org.baeldung.web.exception.MyResourceNotFoundException; | ||||
| 
 | ||||
| /** | ||||
|  * Simple static methods to be called at the start of your own methods to verify correct arguments and state. If the Precondition fails, an {@link HttpStatus} code is thrown | ||||
|  */ | ||||
|  | ||||
| @ -1,26 +1,19 @@ | ||||
| package org.baeldung.common.web; | ||||
| 
 | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomNumeric; | ||||
| import static org.baeldung.web.util.HTTPLinkHeaderUtil.extractURIByRel; | ||||
| import static org.hamcrest.Matchers.is; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertThat; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.junit.Ignore; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import com.google.common.net.HttpHeaders; | ||||
| 
 | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| public abstract class AbstractBasicLiveTest<T extends Serializable> extends AbstractLiveTest<T> { | ||||
| 
 | ||||
|     public AbstractBasicLiveTest(final Class<T> clazzToSet) { | ||||
| @ -104,71 +97,4 @@ public abstract class AbstractBasicLiveTest<T extends Serializable> extends Abst | ||||
|     // find - one | ||||
| 
 | ||||
|     // find - all | ||||
| 
 | ||||
|     // find - all - paginated | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenResourcesAreRetrievedPaged_then200IsReceived() { | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=10"); | ||||
| 
 | ||||
|         assertThat(response.getStatusCode(), is(200)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived() { | ||||
|         final String url = getURL() + "?page=" + randomNumeric(5) + "&size=10"; | ||||
|         final Response response = RestAssured.get(url); | ||||
| 
 | ||||
|         assertThat(response.getStatusCode(), is(404)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources() { | ||||
|         create(); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=10"); | ||||
| 
 | ||||
|         assertFalse(response.body().as(List.class).isEmpty()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext() { | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
| 
 | ||||
|         final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next"); | ||||
|         assertEquals(getURL() + "?page=1&size=2", uriToNextPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage() { | ||||
|         final Response response = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
| 
 | ||||
|         final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev"); | ||||
|         assertNull(uriToPrevPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious() { | ||||
|         create(); | ||||
|         create(); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(getURL() + "?page=1&size=2"); | ||||
| 
 | ||||
|         final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev"); | ||||
|         assertEquals(getURL() + "?page=0&size=2", uriToPrevPage); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable() { | ||||
|         final Response first = RestAssured.get(getURL() + "?page=0&size=2"); | ||||
|         final String uriToLastPage = extractURIByRel(first.getHeader(HttpHeaders.LINK), "last"); | ||||
| 
 | ||||
|         final Response response = RestAssured.get(uriToLastPage); | ||||
| 
 | ||||
|         final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next"); | ||||
|         assertNull(uriToNextPage); | ||||
|     } | ||||
| 
 | ||||
|     // count | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -5,8 +5,6 @@ import static org.hamcrest.Matchers.containsString; | ||||
| import static org.hamcrest.Matchers.equalTo; | ||||
| import static org.hamcrest.Matchers.is; | ||||
| import static org.junit.Assert.assertThat; | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| @ -18,6 +16,9 @@ import org.springframework.http.MediaType; | ||||
| 
 | ||||
| import com.google.common.net.HttpHeaders; | ||||
| 
 | ||||
| import io.restassured.RestAssured; | ||||
| import io.restassured.response.Response; | ||||
| 
 | ||||
| public abstract class AbstractDiscoverabilityLiveTest<T extends Serializable> extends AbstractLiveTest<T> { | ||||
| 
 | ||||
|     public AbstractDiscoverabilityLiveTest(final Class<T> clazzToSet) { | ||||
|  | ||||
| @ -8,7 +8,6 @@ import org.junit.runners.Suite; | ||||
| // @formatter:off | ||||
|     FooDiscoverabilityLiveTest.class | ||||
|     ,FooLiveTest.class | ||||
|     ,FooPageableLiveTest.class | ||||
| }) // | ||||
| public class LiveTestSuiteLiveTest { | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user