Playing with asynchronous requests in Spring
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 8 Dec 2016 20:20:57 +0000 (21:20 +0100)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 8 Dec 2016 20:31:13 +0000 (21:31 +0100)
Possibilites:
Callable
Deferrable + CompletableFuture
Deferrable + ListenableFutureAdapter
RxJava

16 files changed:
SpringJava/RxJava/pom.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-bom/pom.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-global/pom.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-global/src/main/java/de/spring/webservices/domain/Car.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/pom.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/AwesomeBusinessLogic.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/AwesomeBusinessLogicImpl.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CallableCarController.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CarController.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/DeferrableCarController.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/log4j2.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/spring-config.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
SpringJava/RxJava/web-services-spring-rxjava-server/src/test/java/de/spring/webservices/rest/controller/CarControllerIntegrationTest.java [new file with mode: 0644]

diff --git a/SpringJava/RxJava/pom.xml b/SpringJava/RxJava/pom.xml
new file mode 100644 (file)
index 0000000..b8e47a1
--- /dev/null
@@ -0,0 +1,26 @@
+<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/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <groupId>de.spring.webservices</groupId>
+       <artifactId>web-services-spring-rxjava</artifactId>
+       <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+       <name>SPRING RxJava</name>
+       <url>https://gumartinm.name/</url>
+       <description>RxJava with Spring Framework</description>
+       <organization>
+               <name>Gustavo Martin Morcuende</name>
+               <url>https://gumartinm.name/</url>
+       </organization>
+       <scm>
+               <developerConnection>scm:git:https://git.gumartinm.name/JavaForFun/SpringJava/RxJava</developerConnection>
+               <url>https://git.gumartinm.name/JavaForFun/SpringJava/RxJava</url>
+       </scm>
+
+    <modules>
+        <module>web-services-spring-rxjava-bom</module>
+        <module>web-services-spring-rxjava-global</module>
+        <module>web-services-spring-rxjava-server</module>
+    </modules>
+
+</project>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-bom/pom.xml b/SpringJava/RxJava/web-services-spring-rxjava-bom/pom.xml
new file mode 100644 (file)
index 0000000..0703a71
--- /dev/null
@@ -0,0 +1,296 @@
+<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/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <groupId>de.spring.webservices</groupId>
+       <artifactId>web-services-spring-rxjava-bom</artifactId>
+       <packaging>pom</packaging>
+       <version>1.0-SNAPSHOT</version>
+       <name>web-services-spring-rxjava-bom</name>
+       <url>http://gumartinm.name</url>
+       <description>Web Services Spring Framework, RxJava. BOM</description>
+       <organization>
+               <name>Gustavo Martin Morcuende</name>
+               <url>http://www.gumartinm.name</url>
+       </organization>
+       <scm>
+               <developerConnection>scm:git:http://git.gumartinm.name/JavaForFun/SpringJava/RxJava</developerConnection>
+               <url>http://git.gumartinm.name/JavaForFun/SpringJava/RxJava</url>
+       </scm>
+       <properties>
+               <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+               <spring.version>4.2.4.RELEASE</spring.version>
+       </properties>
+       <profiles>
+               <profile>
+                       <id>release</id>
+                       <properties>
+                               <environment.profile>release</environment.profile>
+                       </properties>
+                       <activation>
+                               <activeByDefault>true</activeByDefault>
+                       </activation>
+               </profile>
+       </profiles>
+       <dependencies>
+               <!-- 1/3 Required dependency for log4j 2 with slf4j: binding between log4j 
+                       2 and slf4j -->
+               <dependency>
+                       <groupId>org.apache.logging.log4j</groupId>
+                       <artifactId>log4j-slf4j-impl</artifactId>
+                       <version>2.3</version>
+               </dependency>
+               
+               <!-- 2/3 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin 
+                       (it is the log4j 2 implementation) -->
+               <dependency>
+                       <groupId>org.apache.logging.log4j</groupId>
+                       <artifactId>log4j-core</artifactId>
+                       <version>2.3</version>
+               </dependency>
+               
+               <!-- 3/3 Required dependency for getting rid of commons logging. This is 
+                       the BRIDGE (no binding) between Jakarta Commons Logging (used by Spring) 
+                       and whatever I am using for logging (in this case I am using log4j 2) See: 
+                       http://www.slf4j.org/legacy.html We need exclusions in every dependency using 
+                       Jakarta Commons Logging (see Spring dependencies below) -->
+               <dependency>
+                       <groupId>org.slf4j</groupId>
+                       <artifactId>jcl-over-slf4j</artifactId>
+                       <version>1.7.12</version>
+               </dependency>
+               
+               
+               <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>1</version>
+        </dependency>
+               
+               
+               <dependency>
+                       <groupId>cglib</groupId>
+                       <artifactId>cglib</artifactId>
+                       <version>2.2.2</version>
+               </dependency>
+       </dependencies>
+       <dependencyManagement>
+               <dependencies>
+                       <dependency>
+                               <groupId>org.springframework</groupId>
+                               <artifactId>spring-core</artifactId>
+                               <version>${spring.version}</version>
+                               <!-- Required dependency for getting rid of commons logging and use my 
+                                       own logging library (in my case I decided to use log4j 2 under slf4j) -->
+                               <exclusions>
+                                       <exclusion>
+                                               <groupId>commons-logging</groupId>
+                                               <artifactId>commons-logging</artifactId>
+                                       </exclusion>
+                               </exclusions>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.springframework</groupId>
+                               <artifactId>spring-webmvc</artifactId>
+                               <version>${spring.version}</version>
+                               <!-- Required dependency for getting rid of commons logging and use my 
+                                       own logging library (in my case I decided to use log4j 2 under slf4j) -->
+                               <exclusions>
+                                       <exclusion>
+                                               <groupId>commons-logging</groupId>
+                                               <artifactId>commons-logging</artifactId>
+                                       </exclusion>
+                               </exclusions>
+                       </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-oxm</artifactId>
+                <version>${spring.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+                       <dependency>
+                       <groupId>org.springframework.data</groupId>
+                       <artifactId>spring-data-commons</artifactId>
+                       <version>1.12.5.RELEASE</version>
+                               <!-- Required dependency for getting rid of commons logging and use my 
+                                       own logging library (in my case I decided to use log4j 2 under slf4j) -->
+                               <exclusions>
+                                       <exclusion>
+                                               <groupId>commons-logging</groupId>
+                                               <artifactId>commons-logging</artifactId>
+                                       </exclusion>
+                               </exclusions>
+                       </dependency>
+                       
+                       <!-- Required by spring-webmvc -->
+                       <dependency>
+                               <groupId>javax.servlet</groupId>
+                               <artifactId>javax.servlet-api</artifactId>
+                               <version>4.0.0-b01</version>
+                               <scope>provided</scope>
+                       </dependency>
+                       
+                       <!-- Jackson JSON Processor, required by spring-webmvc. See messageConverters 
+                               in rest-config.xml -->
+                       <dependency>
+                               <groupId>com.fasterxml.jackson.core</groupId>
+                               <artifactId>jackson-databind</artifactId>
+                               <version>2.6.4</version>
+                       </dependency>
+                       
+                       
+                       <!-- Required by spring-context for using JSR-303. See LocalValidatorFactoryBean 
+                               in rest-config.xml -->
+                       <dependency>
+                               <groupId>javax.validation</groupId>
+                               <artifactId>validation-api</artifactId>
+                               <version>1.1.0.Final</version>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.hibernate</groupId>
+                               <artifactId>hibernate-validator</artifactId>
+                               <version>5.2.2.Final</version>
+                       </dependency>
+                       
+                       <!-- Unitary and integration tests -->
+                       <dependency>
+                               <groupId>junit</groupId>
+                               <artifactId>junit</artifactId>
+                               <version>4.12</version>
+                               <scope>test</scope>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.springframework</groupId>
+                               <artifactId>spring-test</artifactId>
+                               <version>${spring.version}</version>
+                               <scope>test</scope>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.mockito</groupId>
+                               <artifactId>mockito-core</artifactId>
+                               <version>2.0.11-beta</version>
+                               <scope>test</scope>
+                       </dependency>
+                       
+                       <!-- Required by MockMvcResultMatchers (spring-test framework) -->
+                       <dependency>
+                               <groupId>org.hamcrest</groupId>
+                               <artifactId>hamcrest-core</artifactId>
+                               <version>1.3</version>
+                               <scope>test</scope>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.hamcrest</groupId>
+                               <artifactId>hamcrest-library</artifactId>
+                               <version>1.3</version>
+                               <scope>test</scope>
+                       </dependency>
+                       <dependency>
+                               <groupId>com.jayway.jsonpath</groupId>
+                               <artifactId>json-path</artifactId>
+                               <version>2.1.0</version>
+                               <scope>test</scope>
+                       </dependency>
+               </dependencies>
+       </dependencyManagement>
+       <build>
+               <pluginManagement>
+                       <plugins>
+                               <plugin>
+                                       <groupId>org.apache.maven.plugins</groupId>
+                                       <artifactId>maven-surefire-plugin</artifactId>
+                                       <version>2.18.1</version>
+                               </plugin>
+                               <plugin>
+                                       <groupId>org.apache.maven.plugins</groupId>
+                                       <artifactId>maven-failsafe-plugin</artifactId>
+                                       <version>2.18.1</version>
+                               </plugin>
+                               <plugin>
+                                       <groupId>org.apache.maven.plugins</groupId>
+                                       <artifactId>maven-war-plugin</artifactId>
+                                       <version>2.6</version>
+                                       <configuration>
+                                               <webResources>
+                                                       <resource>
+                                                               <filtering>true</filtering>
+                                                               <directory>src/main/webapp</directory>
+                                                               <includes>
+                                                                       <include>WEB-INF/web.xml</include>
+                                                               </includes>
+                                                       </resource>
+                                               </webResources>
+                                       </configuration>
+                               </plugin>
+                       </plugins>
+               </pluginManagement>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <version>3.3</version>
+                               <configuration>
+                                       <source>1.8</source>
+                                       <target>1.8</target>
+                                       <encoding>${project.build.sourceEncoding}</encoding>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-resources-plugin</artifactId>
+                               <version>2.7</version>
+                               <configuration>
+                                       <encoding>${project.build.sourceEncoding}</encoding>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-jar-plugin</artifactId>
+                               <version>2.4</version>
+                               <configuration>
+                                       <archive>
+                                               <manifestEntries>
+                                                       <Specification-Title>${project.description}</Specification-Title>
+                                                       <Specification-Version>${project.version}</Specification-Version>
+                                                       <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+                                                       <Implementation-Title>${project.description}</Implementation-Title>
+                                                       <Implementation-Version>${project.version}</Implementation-Version>
+                                                       <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+                                               </manifestEntries>
+                                       </archive>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-surefire-plugin</artifactId>
+                               <configuration>
+                                       <excludes>
+                                               <exclude>**/*IntegrationTest.java</exclude>
+                                       </excludes>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-failsafe-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <goals>
+                                                       <goal>integration-test</goal>
+                                                       <goal>verify</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                               <configuration>
+                                       <includes>
+                                               <include>**/*IntegrationTest.java</include>
+                                       </includes>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-global/pom.xml b/SpringJava/RxJava/web-services-spring-rxjava-global/pom.xml
new file mode 100644 (file)
index 0000000..ce9a818
--- /dev/null
@@ -0,0 +1,12 @@
+<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/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <artifactId>web-services-spring-rxjava-bom</artifactId>
+               <groupId>de.spring.webservices</groupId>
+               <version>1.0-SNAPSHOT</version>
+       </parent>
+       <artifactId>web-services-spring-rxjava-global</artifactId>
+       <name>web-services-spring-rxjava-global</name>
+       <url>http://gumartinm.name</url>
+</project>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-global/src/main/java/de/spring/webservices/domain/Car.java b/SpringJava/RxJava/web-services-spring-rxjava-global/src/main/java/de/spring/webservices/domain/Car.java
new file mode 100644 (file)
index 0000000..cdfaa07
--- /dev/null
@@ -0,0 +1,58 @@
+package de.spring.webservices.domain;
+
+public class Car {
+
+    private final Long id;
+    private final String content;
+
+    // Required by Jackson :/
+    public Car() {
+       this.id = null;
+       this.content = null;
+    }
+    
+    public Car(Long id, String content) {
+        this.id = id;
+        this.content = content;
+    }
+
+    
+       public Long getId() {
+        return id;
+    }
+
+    public String getContent() {
+        return content;
+    }
+    
+    @Override
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result + ((content == null) ? 0 : content.hashCode());
+               result = prime * result + ((id == null) ? 0 : id.hashCode());
+               return result;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               Car other = (Car) obj;
+               if (content == null) {
+                       if (other.content != null)
+                               return false;
+               } else if (!content.equals(other.content))
+                       return false;
+               if (id == null) {
+                       if (other.id != null)
+                               return false;
+               } else if (!id.equals(other.id))
+                       return false;
+               return true;
+       }
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/pom.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/pom.xml
new file mode 100644 (file)
index 0000000..eb967ca
--- /dev/null
@@ -0,0 +1,128 @@
+<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/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <artifactId>web-services-spring-rxjava-bom</artifactId>
+               <groupId>de.spring.webservices</groupId>
+               <version>1.0-SNAPSHOT</version>
+       </parent>
+       <artifactId>web-services-spring-rxjava-server</artifactId>
+       <packaging>war</packaging>
+       <name>web-services-spring-rxjava-server</name>
+       <url>http://gumartinm.name</url>
+       <dependencies>
+               <dependency>
+                       <groupId>de.spring.webservices</groupId>
+                       <artifactId>web-services-spring-rxjava-global</artifactId>
+                       <version>1.0-SNAPSHOT</version>
+               </dependency>
+               
+               <dependency>
+                       <groupId>io.reactivex</groupId>
+                       <artifactId>rxjava</artifactId>
+                       <version>1.2.3</version>
+               </dependency>
+
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-core</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-webmvc</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.data</groupId>
+                       <artifactId>spring-data-commons</artifactId>
+               </dependency>
+               
+               <!-- Required by spring-webmvc -->
+               <dependency>
+                       <groupId>javax.servlet</groupId>
+                       <artifactId>javax.servlet-api</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               
+               <!-- Jackson JSON Processor, required by spring-webmvc. See messageConverters in rest-config.xml -->
+               <dependency>
+                       <groupId>com.fasterxml.jackson.core</groupId>
+                       <artifactId>jackson-databind</artifactId>
+               </dependency>
+
+
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-oxm</artifactId>
+                       <version>4.2.4.RELEASE</version>
+               </dependency>
+
+               
+               <!-- Required by spring-context for using JSR-303. See LocalValidatorFactoryBean in rest-config.xml -->
+               <dependency>
+                       <groupId>javax.validation</groupId>
+                       <artifactId>validation-api</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.hibernate</groupId>
+                       <artifactId>hibernate-validator</artifactId>
+               </dependency>
+               
+               <!-- Unitary and integration tests -->
+               <dependency>
+                       <groupId>junit</groupId>
+                       <artifactId>junit</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-test</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.mockito</groupId>
+                       <artifactId>mockito-core</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               
+               <!-- Required by MockMvcResultMatchers (spring-test framework) -->
+               <dependency>
+                       <groupId>org.hamcrest</groupId>
+                       <artifactId>hamcrest-core</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.hamcrest</groupId>
+                       <artifactId>hamcrest-library</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>com.jayway.jsonpath</groupId>
+                       <artifactId>json-path</artifactId>
+                       <scope>test</scope>
+               </dependency>
+       </dependencies>
+       <build>
+               <finalName>${project.artifactId}</finalName>
+               <resources>
+                       <resource>
+                               <directory>${basedir}/src/main/webapp</directory>
+                               <excludes>
+                                       <exclude>**/*.*</exclude>
+                               </excludes>
+                       </resource>
+                       <resource>
+                               <directory>${basedir}/src/main/resources/</directory>
+                               <includes>
+                                       <include>**/*.*</include>
+                               </includes>
+                       </resource>
+               </resources>
+               
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-war-plugin</artifactId>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/AwesomeBusinessLogic.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/AwesomeBusinessLogic.java
new file mode 100644 (file)
index 0000000..261d4d0
--- /dev/null
@@ -0,0 +1,17 @@
+package de.spring.webservices.rest.business.service;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import de.spring.webservices.domain.Car;
+
+
+public interface AwesomeBusinessLogic {
+
+       public Page<Car> findAll(Pageable pageRequest);
+       
+       public Car findById(long id);
+       
+       public Car create(Car resource);
+       
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/AwesomeBusinessLogicImpl.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/AwesomeBusinessLogicImpl.java
new file mode 100644 (file)
index 0000000..248caf5
--- /dev/null
@@ -0,0 +1,62 @@
+package de.spring.webservices.rest.business.service.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+@Service("awesomeBusinessLogic")
+public class AwesomeBusinessLogicImpl implements AwesomeBusinessLogic {
+       private static final String TEMPLATE = "Car: %s";
+       
+    private final AtomicLong counter = new AtomicLong();
+       
+       @Override
+       public Page<Car> findAll(Pageable pageRequest) {
+        final List<Car> cars = new ArrayList<>();
+        cars.add(new Car(counter.incrementAndGet(), String.format(TEMPLATE, 1)));
+        cars.add(new Car(counter.incrementAndGet(), String.format(TEMPLATE, 2)));
+        cars.add(new Car(counter.incrementAndGet(), String.format(TEMPLATE, 3)));
+        
+        try {
+            Thread.sleep(300000);
+        } catch(InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+               
+               return new PageImpl<>(cars);    
+       }
+
+       @Override
+       public Car findById(long id) {
+               
+        try {
+            Thread.sleep(300000);
+        } catch(InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+               
+               
+               return new Car(counter.incrementAndGet(), String.format(TEMPLATE, id));
+       }
+
+       @Override
+       public Car create(Car resource) {
+               long count = counter.incrementAndGet();
+               
+        try {
+            Thread.sleep(300000);
+        } catch(InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+        
+               return new Car(count, String.format(TEMPLATE, count));
+       }
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CallableCarController.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CallableCarController.java
new file mode 100644 (file)
index 0000000..3092943
--- /dev/null
@@ -0,0 +1,156 @@
+package de.spring.webservices.rest.controller;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+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.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.WebAsyncTask;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+@RestController
+@RequestMapping("/api/callable/cars/")
+public class CallableCarController {
+       private static final Logger LOGGER = LoggerFactory.getLogger(DeferrableCarController.class);
+       private static final int PAGE = 2;
+       private static final int PAGE_SIZE = 10;
+    
+       // With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests.
+       // Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout.
+       private static final long ASYNC_TIMEOUT = 60000;  /* milliseconds */
+       
+       
+       /**
+        * 
+        * WHEN EXCEPTION FROM CALLABLE, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT.
+        * See: https://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/
+        * 
+        * When an Exception is raised by a Callable, it is handled through the
+        * HandlerExceptionResolver mechanism just like exceptions raised by any
+        * other controller method. The more detailed explanation is that the
+        * exception is caught and saved, and the request is dispatched to the
+        * Servlet container where processing resumes and the
+        * HandlerExceptionResolver chain invoked. This also means
+        * that @ExceptionHandler methods will be invoked as usual.
+        * 
+        * 
+        * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as
+        * usual)
+        * 
+        */
+       
+       
+       private final AwesomeBusinessLogic awesomeBusinessLogic;
+       
+       @Inject
+       public CallableCarController(AwesomeBusinessLogic awesomeBusinessLogic) {
+               this.awesomeBusinessLogic = awesomeBusinessLogic;
+       }
+
+    @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public Callable<Page<Car>> cars() {
+       
+       // Relying on the timeout given by Tomcat/Jboss/Jetty/etc. WebAsyncTask if you want to control your timeout (see below)
+       
+//     return new Callable<Page<Car>>() {
+//                     @Override
+//                     public Page<Car> call() throws Exception {
+//                             return awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE));
+//                     }
+//             };
+       
+       // lambda way :)
+       return () -> awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE));
+       
+    }
+
+    @RequestMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public WebAsyncTask<Car> car(@RequestHeader(value = "MY_HEADER", required = false) String specialHeader,
+               @PathVariable("id") long id,
+               @RequestParam Map<String, String> params,
+               @RequestParam(value = "wheel", required = false) String[] wheelParams) {
+               
+       if (specialHeader != null) {
+               LOGGER.info("SPECIAL HEADER: " + specialHeader);
+       }
+        
+       if (params.get("mirror") != null) {
+               LOGGER.info("MIRROR: " + params.get("mirror")); 
+       }
+       
+       if (params.get("window") != null) {
+               LOGGER.info("WINDOW: " + params.get("window"));
+       }
+       
+       if (wheelParams != null) {
+               for(String wheel : wheelParams) {
+                       LOGGER.info(wheel);
+               }
+       }
+       
+       
+       // If you want to control stuff like timeout you must use WebAsyncTask + Callable :)
+       
+//     Callable<Car> callable = new Callable<Car>() {
+//                     @Override
+//                     public Car call() throws Exception {
+//                             return awesomeBusinessLogic.findById(id);
+//                     }
+//             };
+//             return new WebAsyncTask<>(ASYNC_TIMEOUT, callable);
+       
+       // lambda way :)
+        return new WebAsyncTask<>(ASYNC_TIMEOUT, () -> awesomeBusinessLogic.findById(id));
+    }
+    
+    @RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
+               produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
+       @ResponseStatus(HttpStatus.CREATED)
+    public WebAsyncTask<ResponseEntity<Car>> create(@RequestBody Car car) {
+
+       // If you want to control stuff like timeout you must use WebAsyncTask + Callable :)
+
+//     Callable<ResponseEntity<Car>> callable = new Callable<ResponseEntity<Car>>() {
+//                     @Override
+//                     public ResponseEntity<Car> call() throws Exception {
+//                             Car createdCar = awesomeBusinessLogic.create(car);
+//                     
+//                     HttpHeaders headers = new HttpHeaders();
+//                     headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId());
+//                     return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED);
+//                     }
+//             };
+               
+       // lambda way
+               Callable<ResponseEntity<Car>> callable = () -> {
+                       Car createdCar = awesomeBusinessLogic.create(car);
+               
+               HttpHeaders headers = new HttpHeaders();
+               headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId());
+               return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED);
+               };
+               
+               return new WebAsyncTask<>(ASYNC_TIMEOUT, callable);
+    }
+
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CarController.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/CarController.java
new file mode 100644 (file)
index 0000000..2e22f83
--- /dev/null
@@ -0,0 +1,86 @@
+package de.spring.webservices.rest.controller;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+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.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+@RestController
+@RequestMapping("/api/cars/")
+public class CarController {
+       private static final Logger LOGGER = LoggerFactory.getLogger(DeferrableCarController.class);
+       private static final int PAGE = 2;
+       private static final int PAGE_SIZE = 10;
+    
+       private final AwesomeBusinessLogic awesomeBusinessLogic;
+       
+       @Inject
+       public CarController(AwesomeBusinessLogic awesomeBusinessLogic) {
+               this.awesomeBusinessLogic = awesomeBusinessLogic;
+       }
+
+    @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public Page<Car> cars() {
+        return awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE));
+    }
+
+    @RequestMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public Car car(@RequestHeader(value = "MY_HEADER", required = false) String specialHeader,
+               @PathVariable("id") long id,
+               @RequestParam Map<String, String> params,
+               @RequestParam(value = "wheel", required = false) String[] wheelParams) {
+               
+       if (specialHeader != null) {
+               LOGGER.info("SPECIAL HEADER: " + specialHeader);
+       }
+        
+       if (params.get("mirror") != null) {
+               LOGGER.info("MIRROR: " + params.get("mirror")); 
+       }
+       
+       if (params.get("window") != null) {
+               LOGGER.info("WINDOW: " + params.get("window"));
+       }
+       
+       if (wheelParams != null) {
+               for(String wheel : wheelParams) {
+                       LOGGER.info(wheel);
+               }
+       }
+       
+        return awesomeBusinessLogic.findById(id);
+    }
+    
+    @RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
+               produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
+       @ResponseStatus(HttpStatus.CREATED)
+    public ResponseEntity<Car> create(@RequestBody Car car) {
+        Car createdCar = awesomeBusinessLogic.create(car);
+        
+        HttpHeaders headers = new HttpHeaders();
+       headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId());
+               return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED);
+    }
+
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/DeferrableCarController.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/DeferrableCarController.java
new file mode 100644 (file)
index 0000000..6788427
--- /dev/null
@@ -0,0 +1,150 @@
+package de.spring.webservices.rest.controller;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+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.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+@RestController
+@RequestMapping("/api/deferrable/cars/")
+public class DeferrableCarController {
+       private static final Logger LOGGER = LoggerFactory.getLogger(DeferrableCarController.class);
+       private static final int PAGE = 2;
+       private static final int PAGE_SIZE = 10;
+
+       // With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests.
+       // Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout.
+       private static final long ASYNC_TIMEOUT = 60000;  /* milliseconds */
+    
+       /**
+        * 
+        * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE)
+        * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual)
+        * 
+        */
+       
+       private final AwesomeBusinessLogic awesomeBusinessLogic;
+
+       @Inject
+    public DeferrableCarController(AwesomeBusinessLogic awesomeBusinessLogic) {
+               this.awesomeBusinessLogic = awesomeBusinessLogic;
+       }
+
+       @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public DeferredResult<Page<Car>> cars() {
+               
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<Page<Car>> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE)))
+               .thenAcceptAsync(car -> deferredResult.setResult(car))
+               .exceptionally(exception -> {
+                       LOGGER.error("findAll error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+        return deferredResult;
+    }
+
+    @RequestMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public DeferredResult<Car> car(@RequestHeader(value = "MY_HEADER", required = false) String specialHeader,
+                       @PathVariable("id") long id,
+                       @RequestParam Map<String, String> params,
+                       @RequestParam(value = "wheel", required = false) String[] wheelParams) {
+               
+       if (specialHeader != null) {
+               LOGGER.info("SPECIAL HEADER: " + specialHeader);
+       }
+        
+       if (params.get("mirror") != null) {
+               LOGGER.info("MIRROR: " + params.get("mirror")); 
+       }
+       
+       if (params.get("window") != null) {
+               LOGGER.info("WINDOW: " + params.get("window"));
+       }
+       
+       if (wheelParams != null) {
+               for(String wheel : wheelParams) {
+                       LOGGER.info(wheel);
+               }
+       }
+       
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<Car> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> awesomeBusinessLogic.findById(id))
+               .thenAcceptAsync(car -> deferredResult.setResult(car))
+               .exceptionally(exception -> {
+                       
+                       LOGGER.error("findById error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+        return deferredResult;
+    }
+    
+    @RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
+               produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
+       @ResponseStatus(HttpStatus.CREATED)
+    public DeferredResult<ResponseEntity<Car>> create(@RequestBody Car car) {
+       
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<ResponseEntity<Car>> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> {
+                       Car createdCar = awesomeBusinessLogic.create(car);
+                       
+                       HttpHeaders headers = new HttpHeaders();
+                   headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId());
+                   return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED);
+               })
+               .thenAcceptAsync(response -> deferredResult.setResult(response))
+               .exceptionally(exception -> {
+                       
+                       LOGGER.error("create error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+               return deferredResult;
+    }
+
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java
new file mode 100644 (file)
index 0000000..a6d3b08
--- /dev/null
@@ -0,0 +1,150 @@
+package de.spring.webservices.rest.controller;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+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.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+@RestController
+@RequestMapping("/api/rxjava/cars/")
+public class RxJavaCarController {
+       private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaCarController.class);
+       private static final int PAGE = 2;
+       private static final int PAGE_SIZE = 10;
+
+       // With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests.
+       // Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout.
+       private static final long ASYNC_TIMEOUT = 60000;  /* milliseconds */
+    
+       /**
+        * 
+        * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE)
+        * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual)
+        * 
+        */
+       
+       private final AwesomeBusinessLogic awesomeBusinessLogic;
+
+       @Inject
+    public RxJavaCarController(AwesomeBusinessLogic awesomeBusinessLogic) {
+               this.awesomeBusinessLogic = awesomeBusinessLogic;
+       }
+
+       @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public DeferredResult<Page<Car>> cars() {
+               
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<Page<Car>> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE)))
+               .thenAcceptAsync(car -> deferredResult.setResult(car))
+               .exceptionally(exception -> {
+                       LOGGER.error("findAll error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+        return deferredResult;
+    }
+
+    @RequestMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
+    @ResponseStatus(HttpStatus.OK)
+    public DeferredResult<Car> car(@RequestHeader(value = "MY_HEADER", required = false) String specialHeader,
+                       @PathVariable("id") long id,
+                       @RequestParam Map<String, String> params,
+                       @RequestParam(value = "wheel", required = false) String[] wheelParams) {
+               
+       if (specialHeader != null) {
+               LOGGER.info("SPECIAL HEADER: " + specialHeader);
+       }
+        
+       if (params.get("mirror") != null) {
+               LOGGER.info("MIRROR: " + params.get("mirror")); 
+       }
+       
+       if (params.get("window") != null) {
+               LOGGER.info("WINDOW: " + params.get("window"));
+       }
+       
+       if (wheelParams != null) {
+               for(String wheel : wheelParams) {
+                       LOGGER.info(wheel);
+               }
+       }
+       
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<Car> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> awesomeBusinessLogic.findById(id))
+               .thenAcceptAsync(car -> deferredResult.setResult(car))
+               .exceptionally(exception -> {
+                       
+                       LOGGER.error("findById error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+        return deferredResult;
+    }
+    
+    @RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
+               produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
+       @ResponseStatus(HttpStatus.CREATED)
+    public DeferredResult<ResponseEntity<Car>> create(@RequestBody Car car) {
+       
+       // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example.
+       DeferredResult<ResponseEntity<Car>> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
+       CompletableFuture
+               .supplyAsync(() -> {
+                       Car createdCar = awesomeBusinessLogic.create(car);
+                       
+                       HttpHeaders headers = new HttpHeaders();
+                   headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId());
+                   return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED);
+               })
+               .thenAcceptAsync(response -> deferredResult.setResult(response))
+               .exceptionally(exception -> {
+                       
+                       LOGGER.error("create error: ", exception);
+                       
+                       // DO NOT FORGET THE EXCEPTIONS.
+                       // It will trigger the Spring Exception Handler as you know it :)
+                       deferredResult.setErrorResult(exception);
+                       
+                       return null;
+               });
+       
+               return deferredResult;
+    }
+
+}
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/log4j2.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/log4j2.xml
new file mode 100644 (file)
index 0000000..ee36b97
--- /dev/null
@@ -0,0 +1,52 @@
+<?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>
+    
+        <!-- 
+               Specific log level for Spring WEBMVC.
+        -->
+        <Logger name="org.springframework.web" level="INFO" additivity="false">
+            <AppenderRef ref="STDOUT" />
+        </Logger>
+    
+    
+               <!-- 
+               General logging Spring.
+        -->
+        <Logger name="org.springframework" level="INFO" additivity="false">
+            <AppenderRef ref="STDOUT" />
+        </Logger>
+    
+
+               <!-- 
+                       Anything else will be using TRACE logging level.
+                -->        
+        <Root level="INFO">
+            <AppenderRef ref="STDOUT"/>
+        </Root>
+        
+    </Loggers>
+</Configuration>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml
new file mode 100644 (file)
index 0000000..54b8540
--- /dev/null
@@ -0,0 +1,88 @@
+<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.webservices.rest"/>
+       
+       <!--
+               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"/>
+                <util:constant static-field="com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION"/>
+            </array>
+        </property>
+    </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"/>
+
+       <mvc:default-servlet-handler />
+       
+</beans>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/spring-config.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/resources/spring-configuration/spring-config.xml
new file mode 100644 (file)
index 0000000..e50129b
--- /dev/null
@@ -0,0 +1,13 @@
+<?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"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+               
+</beans>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..6c9c1d0
--- /dev/null
@@ -0,0 +1,41 @@
+<?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 REST Services: example</display-name>
+
+    <listener>
+        <listener-class>
+            org.springframework.web.context.ContextLoaderListener
+        </listener-class>
+    </listener>
+
+    <context-param>
+        <param-name>spring.profiles.active</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>
+
+</web-app>
diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/test/java/de/spring/webservices/rest/controller/CarControllerIntegrationTest.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/test/java/de/spring/webservices/rest/controller/CarControllerIntegrationTest.java
new file mode 100644 (file)
index 0000000..06ce410
--- /dev/null
@@ -0,0 +1,108 @@
+package de.spring.webservices.rest.controller;
+
+import static org.hamcrest.Matchers.any;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
+
+
+@Ignore
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration({ "classpath*:spring-configuration/mvc/rest/*.xml"})
+public class CarControllerIntegrationTest {
+       private static final int PAGE = 2;
+       private static final int PAGE_SIZE = 10;
+       private static final String TEMPLATE = "Car: %s";
+       
+       private AwesomeBusinessLogic awesomeBusinessLogic;
+       private CarController controller;
+       private MockMvc mockMvc;
+       
+    @Before
+    public void setup() {
+       awesomeBusinessLogic = mock(AwesomeBusinessLogic.class);
+       controller = new CarController(awesomeBusinessLogic);
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+       @Test
+       public void testWhenGetAllCarsThenRetrieveJsonValues() throws Exception {
+        final List<Car> cars = new ArrayList<>();
+        cars.add(new Car(1L, String.format(TEMPLATE, 1)));
+        cars.add(new Car(2L, String.format(TEMPLATE, 2)));
+        cars.add(new Car(3L, String.format(TEMPLATE, 3)));
+        
+               given(awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE))).willReturn(new PageImpl<>(cars));
+               
+               mockMvc.perform(get("/api/cars/")
+                               .accept(MediaType.APPLICATION_JSON_UTF8))
+               
+               .andExpect(status().isOk())
+               .andExpect(jsonPath("$[0].id", any(Integer.class)))
+               .andExpect(jsonPath("$[0].content", is("Car: 1")))
+               .andExpect(jsonPath("$[1].content", is("Car: 2")))
+               .andExpect(jsonPath("$[1].id", any(Integer.class)))
+               .andExpect(jsonPath("$[2].content", is("Car: 3")))
+               .andExpect(jsonPath("$[2].id", any(Integer.class)))
+               .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
+       }
+       
+       @Test
+       public void testWhenGetOneCarThenRetrieveJsonValue() throws Exception {
+               mockMvc.perform(get("/api/cars/{id}", 1L)
+                               .accept(MediaType.APPLICATION_JSON_UTF8))
+       
+               .andExpect(status().isOk())
+               .andExpect(jsonPath("id", any(Integer.class)))
+               .andExpect(jsonPath("content", is("Car: 1")))
+               .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
+       }
+       
+       @Test
+       public void testWhenCreateNewCarThenRetrieveJsonValue() throws Exception {
+               Car car = new Car(2L, "nothing");
+               mockMvc.perform(post("/api/cars/")
+                               .contentType(MediaType.APPLICATION_JSON_UTF8)
+                               .content(asJsonString(car))
+                               .accept(MediaType.APPLICATION_JSON_UTF8))
+               
+               .andExpect(status().isCreated())
+               .andExpect(jsonPath("id", any(Integer.class)))
+               .andExpect(jsonPath("content", is("Car: 1")))
+               .andExpect(header().string(HttpHeaders.LOCATION, "/api/cars/1"))
+               .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
+       }
+       
+       private static String asJsonString(final Object obj) throws JsonProcessingException {
+               final ObjectMapper mapper = new ObjectMapper();
+               return mapper.writeValueAsString(obj);
+       }
+}