gralde multi module, adding spring-jpa-resources module
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 22 Jan 2017 15:26:58 +0000 (16:26 +0100)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 22 Jan 2017 15:26:58 +0000 (16:26 +0100)
20 files changed:
SpringJava/Gradle/build.gradle
SpringJava/Gradle/settings.gradle
SpringJava/Gradle/spring-jpa-resources/build.gradle [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdDescriptionRevisionController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/AdRevisionController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RepositoryBasedRevisionRestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/RevisionRestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/controllers/ServiceBasedRevisionRestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/handler/UsernameHandler.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/de/spring/example/rest/resource/modules/SpringJpaModule.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/common/exception/NotFoundException.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RepositoryBasedRestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/RestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/resources/log4j2.xml [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/configuration.xml [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/resources/spring-configuration/mvc/rest/rest-config.xml [new file with mode: 0644]
SpringJava/Gradle/spring-jpa-resources/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]

index b0ba2c1..8f4070a 100644 (file)
@@ -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'
index 767e3fa..daa424c 100644 (file)
@@ -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 (file)
index 0000000..d0b774e
--- /dev/null
@@ -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 (file)
index 0000000..93316e3
--- /dev/null
@@ -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<Ad, Long, AdRepository> {
+
+    @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 (file)
index 0000000..4ea0e9d
--- /dev/null
@@ -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<AdDescription, Long, AdDescriptionService> {
+       
+       @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 (file)
index 0000000..b68fc68
--- /dev/null
@@ -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<AdDescription, Long, Integer, AdDescriptionRevisionService> {
+       
+       @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 (file)
index 0000000..53779d1
--- /dev/null
@@ -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<Ad, Long, Integer, AdRepository> {
+
+    @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 (file)
index 0000000..1e1247b
--- /dev/null
@@ -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<T, ID extends Serializable, N extends Number & Comparable<N>, R extends RevisionRepository<T, ID, N>>
+               implements RevisionRestController<T, ID, N> {
+    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<Revision<N, T>> 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 (file)
index 0000000..e6fa2db
--- /dev/null
@@ -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<T, ID extends Serializable, N extends Number & Comparable<N>> {
+
+    /**
+     * 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<Revision<N, T>> 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 (file)
index 0000000..b5b3fbb
--- /dev/null
@@ -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<T, ID extends Serializable, N extends Number & Comparable<N>, S extends RevisionService<T, ID, N>>
+               implements RevisionRestController<T, ID, N> {
+       
+    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<Revision<N, T>> 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 (file)
index 0000000..a0fafc2
--- /dev/null
@@ -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 (file)
index 0000000..ad5f38d
--- /dev/null
@@ -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 (file)
index 0000000..7135761
--- /dev/null
@@ -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 (file)
index 0000000..920a711
--- /dev/null
@@ -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
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>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 :
+ * </p>
+ * <pre>
+ * <code>
+   {@literal @}Override
+   public Sample findById({@literal @}PathVariable String id) {
+        Sample sample = this.repository.findByName(id);
+        if (sample == null) {
+            throw new NotFoundException();
+        }
+        return sample;
+   }
+   </code>
+ * </pre>
+ *
+ *
+ * @param <T>  Your resource class to manage, maybe an entity or DTO class
+ * @param <ID> Resource id type, usually Long or String
+ * @param <R>  The repository class
+ * @see ServiceBasedRestController
+ */
+public abstract class RepositoryBasedRestController<T, ID extends Serializable, R extends PagingAndSortingRepository>
+        implements RestController<T, ID> {
+
+    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<T> findAll() {
+        return repository.findAll();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Page<T> 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<T> findByIds(@RequestParam(value="ids[]") Set<ID> ids){
+        Assert.notNull(ids, "ids list cannot be null");
+        return this.repository.findAll(ids);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void delete() {
+        Iterable<T> 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 (file)
index 0000000..62bb43d
--- /dev/null
@@ -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 <T>  Your resource POJO to manage, maybe an entity or DTO class
+ * @param <ID> Primary resource identifier at webservice level, usually Long or String
+ */
+public interface RestController<T, ID extends Serializable> {
+
+    /**
+     * Create a new resource<br>
+     * REST webservice published : <code>POST /</code>
+     *
+     * @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<br>
+     * REST webservice published : <code>PUT /{id}</code>
+     *
+     * @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 <code>id</code> 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)<br>
+     * REST webservice published : <code>GET /?page=no</code>
+     *
+     * @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<T> findAll();
+
+    /**
+     * Find all resources, and return a paginated and optionaly sorted collection<br>
+     * REST webservice published : <code>GET /search?page=0&amp;size=20 or GET /search?page=0&amp;size=20&amp;direction=desc&amp;properties=name</code>
+     *
+     * @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<T> 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<br>
+     * REST webservice published : <code>GET /{id}</code>
+     *
+     * @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 <code>id</code> does not exist.
+     */
+    @RequestMapping(value = "{id}", method = RequestMethod.GET)
+    @ResponseBody
+    T findById(@PathVariable ID id);
+
+    /**
+     * Find multiple resources by their identifiers<br>
+     * REST webservice published : <code>GET /?ids[]=</code>
+     * <p>
+     * example : <code>/?ids[]=1&amp;ids[]=2&amp;ids[]=3</code>
+     * </p>
+     *
+     * @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<T> findByIds(@RequestParam(value = "ids[]") Set<ID> ids);
+
+    /**
+     * Delete all resources<br>
+     * REST webservice published : <code>DELETE /</code><br>
+     * Return <code>No Content</code> 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<br>
+     * REST webservice published : <code>DELETE /{id}</code><br>
+     * 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 <code>id</code> 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 (file)
index 0000000..ed13138
--- /dev/null
@@ -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
+ * 
+ * <p>
+ * 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
+ * </p>
+ * <p>
+ * 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 :
+ * </p>
+ * <pre>
+ * <code>
+ * {@literal @}Override
+ * public Sample findById({@literal @}PathVariable String id) {
+ * Sample sample = this.service.findByName(id);
+ * if (sample == null) {
+ * throw new NotFoundException();
+ * }
+ * return sample;
+ * }
+ * </code>
+ * </pre>
+ *
+ * @param <T>  Your resource class to manage, maybe an entity or DTO class
+ * @param <ID> Resource id type, usually Long or String
+ * @param <S>  The service class
+ * @see RepositoryBasedRestController
+ */
+public abstract class ServiceBasedRestController<T, ID extends Serializable, S extends CrudService> implements
+        RestController<T, ID> {
+
+    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<T> findAll() {
+        return service.findAll();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Page<T> 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<T> findByIds(@RequestParam(value = "ids[]") Set<ID> 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 (file)
index 0000000..e31be95
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 
+       status: The level of internal Log4j events that should be logged to the console.
+       Valid values for this attribute are "trace", "debug", "info", "warn", "error" and "fatal".
+       
+       monitorInterval: The minimum amount of time, in seconds, that must elapse before the file configuration is checked for changes.
+       
+       
+       see https://logging.apache.org/log4j/2.x/manual/configuration.html
+ -->
+<Configuration status="error" strict="true" monitorInterval="30"
+                name="XMLConfigTest" packages="org.apache.logging.log4j.test">
+                
+       <!--
+               ALL > TRACE > DEBUG > INFO > WARN > ERROR > OFF
+               
+               ERROR by default.
+       -->
+                
+    <Appenders>
+        <Appender type="Console" name="STDOUT">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
+        </Appender>
+    </Appenders>
+    <Loggers>
+    
+
+    <!-- 
+       General logging Spring.
+       -->
+       <Logger name="org.springframework" level="INFO" additivity="false">
+               <AppenderRef ref="STDOUT" />
+       </Logger>
+    
+    <!--
+       How to log Hibernate QUERIES by means of LOG4J/SLF4J:
+               
+       See: org.hibernate.engine.jdbc.spi.SqlStatementLogger
+               
+       1. With hibernate.show_sql=false we stop Hibernate logging to STDOUT.
+       2. DEBUG level (see SqlStatementLogger) is the only way of logging QUERIES using LOG4J.
+               SqlStatementLogger uses log.DEBUG and no other level :(
+       3. JVM requires the following system property: -Dorg.jboss.logging.provider=slf4j for using LOG4J.
+    -->
+       <Logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
+               <AppenderRef ref="STDOUT" />
+       </Logger>
+
+       <!--
+               Logging Hibernate QUERY PARAMETERS requires TRACE level.
+        -->
+       <Logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
+               <AppenderRef ref="STDOUT" />
+       </Logger>
+
+
+       <!-- 
+               Anything else will be using INFO logging level.
+       -->        
+    <Root level="INFO">
+       <AppenderRef ref="STDOUT"/>
+    </Root>
+        
+    </Loggers>
+</Configuration>
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 (file)
index 0000000..fbd171f
--- /dev/null
@@ -0,0 +1,11 @@
+<?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:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+               http://www.springframework.org/schema/beans
+               http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/context
+        http://www.springframework.org/schema/context/spring-context.xsd">
+       
+</beans>
\ 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 (file)
index 0000000..c615178
--- /dev/null
@@ -0,0 +1,115 @@
+<?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:mvc="http://www.springframework.org/schema/mvc"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xsi:schemaLocation="
+               http://www.springframework.org/schema/beans
+               http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/mvc
+        http://www.springframework.org/schema/mvc/spring-mvc.xsd
+        http://www.springframework.org/schema/context
+        http://www.springframework.org/schema/context/spring-context.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+   
+       <!--
+               I am declaring my beans without the automatic annotation. :/
+               Better because we are saving memory but it requires more configuration.
+               
+               See: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
+               <mvc:annotation-driven/>
+        -->
+        
+   
+       <context:annotation-config />
+   
+       <context:component-scan base-package="de.spring.example.rest, org.resthub"/>
+       
+       <!--
+               Required beans for generating XML responses from Java objects using JAXB annotations
+               Jackson also works but it doesn't generate XML with namespaces... O.o
+               
+               This implementation will be slower than the one using Jackson :( but I am going to use it just for WADL generation :)
+       -->    
+    <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
+        <property name="packagesToScan" value="org.jvnet.ws.wadl"/>
+    </bean>
+       <bean id="jaxbConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
+       <constructor-arg ref="jaxbMarshaller" />
+       </bean>
+    
+       <!-- Required beans for generating JSON responses from Java objects -->
+    <bean id="jsonObjectMapperFactory" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
+       p:indentOutput="true" p:failOnEmptyBeans="false">
+        <property name="featuresToDisable">
+            <array>
+                <util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES"/>
+                
+                <!-- Useful when using Java 8 objects like OffsetDateTime.
+                        I want to keep the offset in time zone if it exists.
+                        
+                        LIKE THIS ONE: 2014-07-03 23:27:36+0100
+                -->
+                <util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE"/>
+                
+                <util:constant static-field="com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION"/>
+            </array>
+        </property>
+        <property name="modulesToInstall" ref="customJacksonModules"/>
+    </bean>
+    
+    <util:list id="messageConverters">
+        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" p:objectMapper-ref="jsonObjectMapperFactory"/>
+               <ref bean="jaxbConverter" />
+        <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
+    </util:list>
+
+
+       <bean name="handlerAdapter"
+               class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
+               <property name="webBindingInitializer">
+                       <bean
+                               class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
+                               <!-- It enables us to use JSR-303 -->
+                               <property name="validator">
+                                       <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
+                               </property>
+                       </bean>
+               </property>
+               <property name="messageConverters" ref="messageConverters" />
+               
+               
+               <property name="requestBodyAdvice">
+                       <util:list>
+                               <bean id="requestBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice"/>
+                       </util:list>
+               </property>
+               
+               
+               <property name="responseBodyAdvice">
+                       <util:list>
+                               <bean id="responseBodyAdvice" class="org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice"/>
+                       </util:list>
+               </property>
+       </bean>
+    
+       <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
+
+
+       <util:list id="customJacksonModules">
+               <array>
+                       <value type="java.lang.Class">de.spring.example.rest.resource.modules.SpringJpaModule</value>
+               </array>                
+       </util:list>
+       
+       <mvc:default-servlet-handler />
+       
+       
+       <mvc:interceptors>
+               <bean class="de.spring.example.rest.handler.UsernameHandler"/>
+       </mvc:interceptors>
+       
+</beans>
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 (file)
index 0000000..31173b6
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+         version="2.4">
+
+    <display-name>Spring JPA: example JPA</display-name>
+
+    <listener>
+        <listener-class>
+            org.springframework.web.context.ContextLoaderListener
+        </listener-class>
+    </listener>
+
+    <context-param>
+        <param-name>spring.profiles.default</param-name>
+        <param-value>${environment.profile}</param-value>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>
+            classpath*:spring-configuration/*.xml
+        </param-value>
+    </context-param>
+    
+    <!-- Spring REST support -->
+    <servlet>
+        <servlet-name>spring-rest</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+        <async-supported>true</async-supported>
+        <init-param>
+           <param-name>contextConfigLocation</param-name>
+           <param-value>classpath*:spring-configuration/mvc/rest/*.xml</param-value>
+        </init-param>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>spring-rest</servlet-name>
+        <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O -->
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+    
+    
+    <!--
+       Se serializan objetos Entity al vuelo.
+       
+       1. JACKSON irá accediendo a todas las propiedades de un objeto Entity.
+       2. Según vaya accediendo se irán haciendo queries a base de datos. NO ANTES porque se hace en
+       modo lazy, lo que quiere decir que tenemos el objeto Entity pero sus datos (lo que se obtiene
+       después de hacer las queries) están vacíos. Hasta que no se accede a una propiedad determinada
+       del objeto Entity no se hará realmente la query en base de datos. 
+     -->
+    <filter>
+        <filter-name>OpenEntityManagerInViewFilter</filter-name>
+        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>OpenEntityManagerInViewFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    
+
+</web-app>