--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+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;
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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);
+
+}
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+<?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>
--- /dev/null
+<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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+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);
+ }
+}