From c7b1a0fc0ac658ceaaa2689d05f29dd166820d57 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Sun, 22 Jan 2017 16:26:58 +0100 Subject: [PATCH] gralde multi module, adding spring-jpa-resources module --- SpringJava/Gradle/build.gradle | 4 + SpringJava/Gradle/settings.gradle | 2 +- .../Gradle/spring-jpa-resources/build.gradle | 32 +++++ .../example/rest/controllers/AdController.java | 28 ++++ .../rest/controllers/AdDescriptionController.java | 24 ++++ .../AdDescriptionRevisionController.java | 24 ++++ .../rest/controllers/AdRevisionController.java | 21 +++ .../RepositoryBasedRevisionRestController.java | 47 +++++++ .../rest/controllers/RevisionRestController.java | 32 +++++ .../ServiceBasedRevisionRestController.java | 58 ++++++++ .../example/rest/handler/UsernameHandler.java | 32 +++++ .../rest/resource/modules/SpringJpaModule.java | 11 ++ .../common/exception/NotFoundException.java | 24 ++++ .../controller/RepositoryBasedRestController.java | 155 +++++++++++++++++++++ .../org/resthub/web/controller/RestController.java | 120 ++++++++++++++++ .../web/controller/ServiceBasedRestController.java | 148 ++++++++++++++++++++ .../src/main/resources/log4j2.xml | 66 +++++++++ .../spring-configuration/configuration.xml | 11 ++ .../spring-configuration/mvc/rest/rest-config.xml | 115 +++++++++++++++ .../src/main/webapp/WEB-INF/web.xml | 61 ++++++++ 20 files changed, 1014 insertions(+), 1 deletion(-) create mode 100644 SpringJava/Gradle/spring-jpa-resources/build.gradle create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionRevisionController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdRevisionController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RepositoryBasedRevisionRestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RevisionRestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/ServiceBasedRevisionRestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/handler/UsernameHandler.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/resource/modules/SpringJpaModule.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/common/exception/NotFoundException.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RepositoryBasedRestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/resources/log4j2.xml create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/configuration.xml create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/mvc/rest/rest-config.xml create mode 100644 SpringJava/Gradle/spring-jpa-resources/src/main/webapp/WEB-INF/web.xml diff --git a/SpringJava/Gradle/build.gradle b/SpringJava/Gradle/build.gradle index b0ba2c1..8f4070a 100644 --- a/SpringJava/Gradle/build.gradle +++ b/SpringJava/Gradle/build.gradle @@ -21,6 +21,10 @@ project.ext { // Validation API. JSR-303 validationAPIVersion='1.1.0.Final' + // Provided dependencies. + // Provided dependencies are compileOnly in Gradle. Unlike maven they are not included on the test classpath. Gradle sucks?! + servletApiVersion='4.0.0-b01' + // LOG4J2 dependencies slf4jVersion='2.7' log4jCoreVersion='2.7' diff --git a/SpringJava/Gradle/settings.gradle b/SpringJava/Gradle/settings.gradle index 767e3fa..daa424c 100644 --- a/SpringJava/Gradle/settings.gradle +++ b/SpringJava/Gradle/settings.gradle @@ -1 +1 @@ -include ':spring-jpa-persistence', ':spring-jpa-services' +include ':spring-jpa-persistence', ':spring-jpa-services', ':spring-jpa-resources' diff --git a/SpringJava/Gradle/spring-jpa-resources/build.gradle b/SpringJava/Gradle/spring-jpa-resources/build.gradle new file mode 100644 index 0000000..d0b774e --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/build.gradle @@ -0,0 +1,32 @@ +dependencies { + + compile(project(":spring-jpa-services")) + + // REST API + compile("org.springframework:spring-webmvc:${springVersion}") + compile("org.springframework:spring-oxm:${springVersion}") + + // Provided dependency. Unlike maven they are not included on the test classpath. Gradle sucks?! + compileOnly("javax.servlet:javax.servlet-api:${servletApiVersion}") + + + + // Jackson JSON Processor, required by spring-webmvc. See messageConverters in rest-config.xml + // Non required dependency. It is already declared in jackson-datatype-jsr310 + // compile("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") + + // Jackson dependency required for serializing and deserializing LocalDateTime, + // LocalDate, etc, etc objects. + compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVersion}") + + // Jackson dependency required for serializing and deserializing org.joda.time.DateTime objects. + // See: org.springframework.data.history.Revision getRevisionDate + compile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jacksonVersion}") + + + + // Integration tests + testCompile("org.springframework:spring-test:${springVersion}") { + exclude group: 'commons-logging', module: 'commons-logging' + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdController.java new file mode 100644 index 0000000..93316e3 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdController.java @@ -0,0 +1,28 @@ +package de.spring.example.rest.controllers; + +import javax.inject.Inject; + +import org.resthub.web.controller.RepositoryBasedRestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.spring.example.persistence.domain.Ad; +import de.spring.example.persistence.repository.AdRepository; + +@RestController +@RequestMapping("/ads/") +public class AdController extends RepositoryBasedRestController { + + @Override + @Inject + public void setRepository(AdRepository repository) { + this.repository = repository; + } + + // I do not have to do anything here because all I need is implemented by RepositoryBasedRestController :) + + // @Transactional is implemented by org.springframework.data.jpa.repository.support.SimpleJpaRepository + // By default, SimpleJpaRepository will be automatically implemented by my + // Spring JPA repositories: AdRepository and AdDescriptionRepository. + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionController.java new file mode 100644 index 0000000..4ea0e9d --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionController.java @@ -0,0 +1,24 @@ +package de.spring.example.rest.controllers; + +import javax.inject.Inject; + +import org.resthub.web.controller.ServiceBasedRestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.spring.example.persistence.domain.AdDescription; +import de.spring.example.services.AdDescriptionService; + +@RestController +@RequestMapping("/ad-descriptions/") +public class AdDescriptionController extends ServiceBasedRestController { + + @Override + @Inject + public void setService(AdDescriptionService adDescriptionService) { + this.service = adDescriptionService; + } + + // I do not have to do anything here because all I need is implemented by ServiceBasedRestController :) + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionRevisionController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionRevisionController.java new file mode 100644 index 0000000..b68fc68 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionRevisionController.java @@ -0,0 +1,24 @@ +package de.spring.example.rest.controllers; + +import javax.inject.Inject; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.spring.example.persistence.domain.AdDescription; +import de.spring.example.services.AdDescriptionRevisionService; + +@RestController +@RequestMapping("/ad-descriptions/") +public class AdDescriptionRevisionController + extends ServiceBasedRevisionRestController { + + @Override + @Inject + public void setService(AdDescriptionRevisionService adDescriptionRevisionService) { + this.service = adDescriptionRevisionService; + } + + // I do not have to do anything here because all I need is implemented by ServiceBasedRevisionRestController :) + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdRevisionController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdRevisionController.java new file mode 100644 index 0000000..53779d1 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdRevisionController.java @@ -0,0 +1,21 @@ +package de.spring.example.rest.controllers; + +import javax.inject.Inject; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.spring.example.persistence.domain.Ad; +import de.spring.example.persistence.repository.AdRepository; + +@RestController +@RequestMapping("/ads/") +public class AdRevisionController extends + RepositoryBasedRevisionRestController { + + @Override + @Inject + public void setRepository(AdRepository repository) { + this.repository = repository; + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RepositoryBasedRevisionRestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RepositoryBasedRevisionRestController.java new file mode 100644 index 0000000..1e1247b --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RepositoryBasedRevisionRestController.java @@ -0,0 +1,47 @@ +package de.spring.example.rest.controllers; + +import java.io.Serializable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.history.Revision; +import org.springframework.data.repository.history.RevisionRepository; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +public abstract class RepositoryBasedRevisionRestController, R extends RevisionRepository> + implements RevisionRestController { + protected R repository; + + protected Logger logger = LoggerFactory.getLogger(RepositoryBasedRevisionRestController.class); + + /** + * You should override this setter in order to inject your repository with @Inject annotation + * + * @param repository The repository to be injected + */ + public void setRepository(R repository) { + this.repository = repository; + } + + + @Override + public Page> findRevisionsPaginated(@PathVariable ID id, + @RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties) { + Assert.isTrue(page > 0, "Page index must be greater than 0"); + Assert.isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC"); + if(direction.isEmpty()) { + return this.repository.findRevisions(id, new PageRequest(page - 1, size)); + } else { + Assert.notNull(properties); + return this.repository.findRevisions(id, new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString(direction.toUpperCase()), properties.split(",")))); + } + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RevisionRestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RevisionRestController.java new file mode 100644 index 0000000..e6fa2db --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RevisionRestController.java @@ -0,0 +1,32 @@ +package de.spring.example.rest.controllers; + +import java.io.Serializable; + +import org.springframework.data.domain.Page; +import org.springframework.data.history.Revision; +import org.springframework.web.bind.annotation.PathVariable; +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; + +public interface RevisionRestController> { + + /** + * Returns a {@link Page} of revisions for the entity with the given id + * + * @param id The identifier of the resource to find. + * @param page Page number starting from 0. default to 0 + * @param size Number of resources by pages. default to 10 + * @param direction Optional sort direction, could be "asc" or "desc" + * @param properties Ordered list of comma separated properties used for sorting results. At least one property should be provided if direction is specified + * @return OK http status code if the request has been correctly processed, with the a paginated collection of all resource enclosed in the body. + */ + @RequestMapping(value="{id}/revisions/", method = RequestMethod.GET) + @ResponseBody + public Page> findRevisionsPaginated(@PathVariable ID id, + @RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties); +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/ServiceBasedRevisionRestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/ServiceBasedRevisionRestController.java new file mode 100644 index 0000000..b5b3fbb --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/ServiceBasedRevisionRestController.java @@ -0,0 +1,58 @@ +package de.spring.example.rest.controllers; + +import java.io.Serializable; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.history.Revision; +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; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import de.spring.example.services.RevisionService; + +public abstract class ServiceBasedRevisionRestController, S extends RevisionService> + implements RevisionRestController { + + protected S service; + + /** + * You should override this setter in order to inject your service with @Inject annotation + * + * @param service The service to be injected + */ + public void setService(S service) { + this.service = service; + } + + + /** + * Returns a {@link Page} of revisions for the entity with the given id + * + * @param page Page number starting from 0. default to 0 + * @param size Number of resources by pages. default to 10 + * @param direction Optional sort direction, could be "asc" or "desc" + * @param properties Ordered list of comma separeted properies used for sorting resulats. At least one property should be provided if direction is specified + * @return OK http status code if the request has been correctly processed, with the a paginated collection of all resource enclosed in the body. + */ + @RequestMapping(value="{id}/revisions/", method = RequestMethod.GET) + @ResponseBody + public Page> findRevisionsPaginated(@PathVariable ID id, + @RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties) { + Assert.isTrue(page > 0, "Page index must be greater than 0"); + Assert.isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC"); + if(direction.isEmpty()) { + return this.service.findRevisions(id, new PageRequest(page - 1, size)); + } else { + Assert.notNull(properties); + return this.service.findRevisions(id, new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString(direction.toUpperCase()), properties.split(",")))); + } + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/handler/UsernameHandler.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/handler/UsernameHandler.java new file mode 100644 index 0000000..a0fafc2 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/handler/UsernameHandler.java @@ -0,0 +1,32 @@ +package de.spring.example.rest.handler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import de.spring.example.context.UsernameThreadContext; + +public class UsernameHandler extends HandlerInterceptorAdapter { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + final String userName = request.getHeader(UsernameThreadContext.USERNAME_HEADER); + + if (userName != null) { + UsernameThreadContext.setUsername(userName); + } else { + UsernameThreadContext.clearUsername(); + } + + return super.preHandle(request, response, handler); + } + + @Override + public void afterCompletion( + HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + UsernameThreadContext.clearUsername(); + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/resource/modules/SpringJpaModule.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/resource/modules/SpringJpaModule.java new file mode 100644 index 0000000..ad5f38d --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/resource/modules/SpringJpaModule.java @@ -0,0 +1,11 @@ +package de.spring.example.rest.resource.modules; + +import com.fasterxml.jackson.databind.module.SimpleModule; + +public class SpringJpaModule extends SimpleModule { + + @Override + public void setupModule(final SetupContext context) { + + } +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/common/exception/NotFoundException.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/common/exception/NotFoundException.java new file mode 100644 index 0000000..7135761 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/common/exception/NotFoundException.java @@ -0,0 +1,24 @@ +package org.resthub.common.exception; + +/** + * Exception thrown when not result was found (for example findById with null return value) + */ +public class NotFoundException extends RuntimeException { + + public NotFoundException() { + super(); + } + + public NotFoundException(final String message, final Throwable cause) { + super(message, cause); + } + + public NotFoundException(final String message) { + super(message); + } + + public NotFoundException(final Throwable cause) { + super(cause); + } + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RepositoryBasedRestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RepositoryBasedRestController.java new file mode 100644 index 0000000..920a711 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RepositoryBasedRestController.java @@ -0,0 +1,155 @@ +package org.resthub.web.controller; + +import org.resthub.common.exception.NotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.io.Serializable; +import java.util.Set; + +/** + * Abstract REST controller using a repository implementation + *

+ * You should extend this class when you want to use a 2 layers pattern : Repository and Controller. This is the default + * controller implementation to use if you have no service (also called business) layer. You will be able to transform + * it to a ServiceBasedRestController later easily if needed. + *

+ * + *

Default implementation uses "id" field (usually a Long) in order to identify resources in web request. + * If your want to identity resources by a slug (human readable identifier), your should override findById() method with for example : + *

+ *
+ * 
+   {@literal @}Override
+   public Sample findById({@literal @}PathVariable String id) {
+        Sample sample = this.repository.findByName(id);
+        if (sample == null) {
+            throw new NotFoundException();
+        }
+        return sample;
+   }
+   
+ * 
+ * + * + * @param Your resource class to manage, maybe an entity or DTO class + * @param Resource id type, usually Long or String + * @param The repository class + * @see ServiceBasedRestController + */ +public abstract class RepositoryBasedRestController + implements RestController { + + protected R repository; + + protected Logger logger = LoggerFactory.getLogger(RepositoryBasedRestController.class); + + /** + * You should override this setter in order to inject your repository with @Inject annotation + * + * @param repository The repository to be injected + */ + public void setRepository(R repository) { + this.repository = repository; + } + + /** + * {@inheritDoc} + */ + @Override + public T create(@RequestBody T resource) { + return (T)this.repository.save(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public T update(@PathVariable ID id, @RequestBody T resource) { + Assert.notNull(id, "id cannot be null"); + + T retrievedResource = this.findById(id); + if (retrievedResource == null) { + throw new NotFoundException(); + } + + return (T)this.repository.save(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findAll() { + return repository.findAll(); + } + + /** + * {@inheritDoc} + */ + @Override + public Page findPaginated(@RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties) { + Assert.isTrue(page > 0, "Page index must be greater than 0"); + Assert.isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC"); + if(direction.isEmpty()) { + return this.repository.findAll(new PageRequest(page - 1, size)); + } else { + Assert.notNull(properties); + return this.repository.findAll(new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString(direction.toUpperCase()), properties.split(",")))); + } + } + + /** + * {@inheritDoc} + */ + @Override + public T findById(@PathVariable ID id) { + T entity = (T)this.repository.findOne(id); + if (entity == null) { + throw new NotFoundException(); + } + + return entity; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findByIds(@RequestParam(value="ids[]") Set ids){ + Assert.notNull(ids, "ids list cannot be null"); + return this.repository.findAll(ids); + } + + /** + * {@inheritDoc} + */ + @Override + public void delete() { + Iterable list = repository.findAll(); + for (T entity : list) { + repository.delete(entity); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void delete(@PathVariable ID id) { + T resource = this.findById(id); + this.repository.delete(resource); + } + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RestController.java new file mode 100644 index 0000000..62bb43d --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RestController.java @@ -0,0 +1,120 @@ +package org.resthub.web.controller; + +import org.resthub.common.exception.NotFoundException; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.io.Serializable; +import java.util.Set; + +/** + * REST controller interface + * + * @param Your resource POJO to manage, maybe an entity or DTO class + * @param Primary resource identifier at webservice level, usually Long or String + */ +public interface RestController { + + /** + * Create a new resource
+ * REST webservice published : POST / + * + * @param resource The resource to create + * @return CREATED http status code if the request has been correctly processed, with updated resource enclosed in the body, usually with and additional identifier automatically created by the database + */ + @RequestMapping(method = RequestMethod.POST) + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + T create(@RequestBody T resource); + + /** + * Update an existing resource
+ * REST webservice published : PUT /{id} + * + * @param id The identifier of the resource to update, usually a Long or String identifier. It is explicitely provided in order to handle cases where the identifier could be changed. + * @param resource The resource to update + * @return OK http status code if the request has been correctly processed, with the updated resource enclosed in the body + * @throws NotFoundException when resource id does not exist. + */ + @RequestMapping(value = "{id}", method = RequestMethod.PUT) + @ResponseBody + T update(@PathVariable ID id, @RequestBody T resource); + + /** + * Find all resources, and return the full collection (plain list not paginated)
+ * REST webservice published : GET /?page=no + * + * @return OK http status code if the request has been correctly processed, with the list of all resource enclosed in the body. + * Be careful, this list should be big since it will return ALL resources. In this case, consider using paginated findAll method instead. + */ + @RequestMapping(method = RequestMethod.GET, params = "page=no") + @ResponseBody + Iterable findAll(); + + /** + * Find all resources, and return a paginated and optionaly sorted collection
+ * REST webservice published : GET /search?page=0&size=20 or GET /search?page=0&size=20&direction=desc&properties=name + * + * @param page Page number starting from 0. default to 0 + * @param size Number of resources by pages. default to 10 + * @param direction Optional sort direction, could be "asc" or "desc" + * @param properties Ordered list of comma separeted properies used for sorting resulats. At least one property should be provided if direction is specified + * @return OK http status code if the request has been correctly processed, with the a paginated collection of all resource enclosed in the body. + */ + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + Page findPaginated(@RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "ASC") String direction, + @RequestParam(value = "properties", required = false) String properties); + + /** + * Find a resource by its identifier
+ * REST webservice published : GET /{id} + * + * @param id The identifier of the resouce to find + * @return OK http status code if the request has been correctly processed, with resource found enclosed in the body + * @throws NotFoundException when resource id does not exist. + */ + @RequestMapping(value = "{id}", method = RequestMethod.GET) + @ResponseBody + T findById(@PathVariable ID id); + + /** + * Find multiple resources by their identifiers
+ * REST webservice published : GET /?ids[]= + *

+ * example : /?ids[]=1&ids[]=2&ids[]=3 + *

+ * + * @param ids List of ids to retrieve + * @return OK http status code with list of retrieved resources. Not found resources are ignored: + * no Exception thrown. List is empty if no resource found with any of the given ids. + */ + @RequestMapping(method = RequestMethod.GET, params = "ids[]") + @ResponseBody + Iterable findByIds(@RequestParam(value = "ids[]") Set ids); + + /** + * Delete all resources
+ * REST webservice published : DELETE /
+ * Return No Content http status code if the request has been correctly processed + */ + @RequestMapping(method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.NO_CONTENT) + void delete(); + + /** + * Delete a resource by its identifier
+ * REST webservice published : DELETE /{id}
+ * Return No Content http status code if the request has been correctly processed + * + * @param id The identifier of the resource to delete + * @throws NotFoundException when resource id does not exist. + */ + @RequestMapping(value = "{id}", method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.NO_CONTENT) + void delete(@PathVariable ID id); + +} diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java new file mode 100644 index 0000000..ed13138 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java @@ -0,0 +1,148 @@ +package org.resthub.web.controller; + +import java.io.Serializable; +import java.util.Set; + +import org.resthub.common.exception.NotFoundException; +import org.resthub.common.service.CrudService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Abstract REST controller using a service implementation + * + *

+ * You should extend this class when you want to use a 3 layers pattern : Repository, Service and Controller + * If you don't have a real service (also called business layer), consider using RepositoryBasedRestController + *

+ *

+ * Default implementation uses "id" field (usually a Long) in order to identify resources in web request. + * If your want to identity resources by a slug (human readable identifier), your should override findById() method with for example : + *

+ *
+ * 
+ * {@literal @}Override
+ * public Sample findById({@literal @}PathVariable String id) {
+ * Sample sample = this.service.findByName(id);
+ * if (sample == null) {
+ * throw new NotFoundException();
+ * }
+ * return sample;
+ * }
+ * 
+ * 
+ * + * @param Your resource class to manage, maybe an entity or DTO class + * @param Resource id type, usually Long or String + * @param The service class + * @see RepositoryBasedRestController + */ +public abstract class ServiceBasedRestController implements + RestController { + + protected S service; + + /** + * You should override this setter in order to inject your service with @Inject annotation + * + * @param service The service to be injected + */ + public void setService(S service) { + this.service = service; + } + + /** + * {@inheritDoc} + */ + @Override + public T create(@RequestBody T resource) { + return (T) this.service.create(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public T update(@PathVariable ID id, @RequestBody T resource) { + Assert.notNull(id, "id cannot be null"); + + T retreivedResource = this.findById(id); + if (retreivedResource == null) { + throw new NotFoundException(); + } + + return (T) this.service.update(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findAll() { + return service.findAll(); + } + + /** + * {@inheritDoc} + */ + @Override + public Page findPaginated(@RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties) { + Assert.isTrue(page > 0, "Page index must be greater than 0"); + Assert.isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC"); + if (direction.isEmpty()) { + return this.service.findAll(new PageRequest(page - 1, size)); + } else { + Assert.notNull(properties); + return this.service.findAll(new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString(direction.toUpperCase()), properties.split(",")))); + } + } + + /** + * {@inheritDoc} + */ + @Override + public T findById(@PathVariable ID id) { + T resource = (T) this.service.findById(id); + if (resource == null) { + throw new NotFoundException(); + } + + return resource; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findByIds(@RequestParam(value = "ids[]") Set ids) { + Assert.notNull(ids, "ids list cannot be null"); + return this.service.findByIds(ids); + } + + + /** + * {@inheritDoc} + */ + @Override + public void delete() { + this.service.deleteAllWithCascade(); + } + + /** + * {@inheritDoc} + */ + @Override + public void delete(@PathVariable ID id) { + T resource = this.findById(id); + this.service.delete(resource); + } +} + diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/resources/log4j2.xml b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/log4j2.xml new file mode 100644 index 0000000..e31be95 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/log4j2.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/configuration.xml b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/configuration.xml new file mode 100644 index 0000000..fbd171f --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/configuration.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/mvc/rest/rest-config.xml b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/mvc/rest/rest-config.xml new file mode 100644 index 0000000..c615178 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/mvc/rest/rest-config.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + de.spring.example.rest.resource.modules.SpringJpaModule + + + + + + + + + + + diff --git a/SpringJava/Gradle/spring-jpa-resources/src/main/webapp/WEB-INF/web.xml b/SpringJava/Gradle/spring-jpa-resources/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..31173b6 --- /dev/null +++ b/SpringJava/Gradle/spring-jpa-resources/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,61 @@ + + + + Spring JPA: example JPA + + + + org.springframework.web.context.ContextLoaderListener + + + + + spring.profiles.default + ${environment.profile} + contextConfigLocation + + classpath*:spring-configuration/*.xml + + + + + + spring-rest + org.springframework.web.servlet.DispatcherServlet + 1 + true + + contextConfigLocation + classpath*:spring-configuration/mvc/rest/*.xml + + + + + spring-rest + + /* + + + + + + OpenEntityManagerInViewFilter + org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter + + + OpenEntityManagerInViewFilter + /* + + + + -- 2.1.4