--- /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-rest</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-client</artifactId>
+ <name>web-services-spring-rest-client</name>
+ <url>http://gumartinm.name</url>
+ <dependencies>
+ <dependency>
+ <groupId>de.spring.webservices</groupId>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </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>
+
+ <!-- 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>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/resources/</directory>
+ <includes>
+ <include>**/*.*</include>
+ </includes>
+ </resource>
+ </resources>
+ </build>
+</project>
\ No newline at end of file
--- /dev/null
+package de.spring.webservices.main;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import de.spring.webservices.rest.business.service.BusinessService;
+
+
+/**
+ * This class is used just like a nice example about how to write and run client
+ * code which will send data to and from the Web Services.
+ *
+ */
+public class MainTest {
+ public ApplicationContext context;
+
+ /**
+ * @param args
+ */
+ public static void main(final String[] args) {
+ final MainTest test = new MainTest();
+
+ test.context = new ClassPathXmlApplicationContext(
+ "classpath:spring-configuration/rest-config.xml");
+
+ final BusinessService example =
+ (BusinessService) test.context.getBean("businessService");
+
+ example.doSomethingWithCars();
+
+ example.doSomethingWithCar(66L);
+
+ example.createsNewCar();
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.business.service;
+
+
+public interface BusinessService {
+
+ public void doSomethingWithCars();
+
+ public void doSomethingWithCar(long id);
+
+ public void createsNewCar();
+}
--- /dev/null
+package de.spring.webservices.rest.business.service.impl;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.BusinessService;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+@Service("businessService")
+public class BusinessServiceImpl implements BusinessService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BusinessServiceImpl.class);
+
+ private final CarClientService carClientService;
+
+ @Autowired
+ public BusinessServiceImpl(CarClientService carClientService) {
+ this.carClientService = carClientService;
+ }
+
+
+ @Override
+ public void doSomethingWithCars() {
+ List<Car> cars = carClientService.doGetCars();
+ LOGGER.info("Retrieved cars");
+ for (Car car : cars) {
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+ }
+
+ @Override
+ public void doSomethingWithCar(long id) {
+ Car car = carClientService.doGetCar(id);
+ LOGGER.info("Retrieved car");
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+
+ @Override
+ public void createsNewCar() {
+ Car newCar = new Car(666L, "just_a_test");
+
+ Car car = carClientService.doNewCar(newCar);
+ LOGGER.info("New car");
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.client.service;
+
+import java.util.List;
+
+import de.spring.webservices.domain.Car;
+
+public interface CarClientService {
+
+ public List<Car> doGetCars();
+
+ public Car doGetCar(long id);
+
+ public Car doNewCar(Car car);
+}
--- /dev/null
+package de.spring.webservices.rest.client.service.impl;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+@Service("carClientService")
+public class CarClientServiceImpl implements CarClientService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CarClientServiceImpl.class);
+
+ private final String apiCarsUrl;
+ private final String apiCarUrl;
+ private final RestTemplate restTemplate;
+
+ @Autowired
+ public CarClientServiceImpl(@Value("${url.base}${url.cars}") String apiCarsUrl,
+ @Value("${url.base}${url.car}") String apiCarUrl, RestTemplate restTemplate) {
+ this.apiCarsUrl = apiCarsUrl;
+ this.apiCarUrl = apiCarUrl;
+ this.restTemplate = restTemplate;
+ }
+
+
+ @Override
+ public List<Car> doGetCars() {
+ ResponseEntity<Car[]> responseEntity = restTemplate.getForEntity(apiCarsUrl, Car[].class);
+
+ return Arrays.asList(responseEntity.getBody());
+ }
+
+ @Override
+ public Car doGetCar(long id) {
+ ResponseEntity<Car> responseEntity = restTemplate.getForEntity(
+ apiCarUrl.replace(":id", String.valueOf(id)), Car.class);
+
+ return responseEntity.getBody();
+ }
+
+ @Override
+ public Car doNewCar(Car car) {
+ ResponseEntity<Car> responseEntity = restTemplate.postForEntity(apiCarsUrl, car, Car.class);
+ URI newCarLocation = responseEntity.getHeaders().getLocation();
+ LOGGER.info("new car location: " + newCarLocation.getPath());
+
+ return responseEntity.getBody();
+ }
+}
--- /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
+url.base = http://localhost:8080/web-services-spring-rest-server/spring-rest/
+
+url.cars = api/cars/
+url.car = api/cars/:id
\ No newline at end of file
--- /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"
+ 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/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">
+
+
+ <context:annotation-config />
+
+ <context:component-scan base-package="de.spring.webservices.rest"/>
+
+ <context:property-placeholder location="classpath:rest.properties" />
+
+ <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"/>
+ <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
+ </util:list>
+
+ <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
+ <constructor-arg ref="messageConverters" />
+ </bean>
+
+</beans>
--- /dev/null
+package de.spring.webservices.rest.business.service;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.impl.BusinessServiceImpl;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+public class BusinessServiceTest {
+
+ private CarClientService carClientService;
+ private BusinessService businessService;
+
+ @Before
+ public void createTest() {
+ carClientService = mock(CarClientService.class);
+ businessService = new BusinessServiceImpl(carClientService);
+ }
+
+ @Test
+ public void whenDoSomethingWithCarsThenInvokeDoGetCars() {
+ Car expectedOne = new Car(66L, "test");
+ Car expectedTwo = new Car(99L, "example");
+ List<Car> expected = new ArrayList<>();
+ expected.add(expectedOne);
+ expected.add(expectedTwo);
+ when(carClientService.doGetCars()).thenReturn(expected);
+
+ businessService.doSomethingWithCars();
+
+ verify(carClientService, times(1)).doGetCars();
+ }
+
+ @Test
+ public void whenDoSomethingWithOneCarhenInvokeDoGetCar() {
+ Long id = 66L;
+ Car expected = new Car(66L, "test");
+
+ when(carClientService.doGetCar(id)).thenReturn(expected);
+
+ businessService.doSomethingWithCar(id);
+
+ verify(carClientService, times(1)).doGetCar(id);
+ }
+
+ @Test
+ public void whenCreateNewCarThenCreateNewOne() {
+ Car expected = new Car(66L, "test");
+ ArgumentCaptor<Car> argCar = ArgumentCaptor.forClass(Car.class);
+
+ when(carClientService.doNewCar(argCar.capture())).thenReturn(expected);
+
+ businessService.createsNewCar();
+
+ verify(carClientService, times(1)).doNewCar(argCar.getValue());
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.client.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import de.spring.webservices.domain.Car;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath*:spring-configuration/rest-config.xml")
+public class CarClientServiceIntegrationTest {
+
+ @Value("${url.base}${url.cars}")
+ private String apiCarsUrl;
+
+ @Value("${url.base}${url.car}")
+ private String apiCarUrl;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Autowired
+ private CarClientService carClientService;
+
+ @Autowired
+ private Jackson2ObjectMapperFactoryBean jsonObjectMapperFactory;
+
+ private MockRestServiceServer mockServer;
+
+ @Before
+ public void createTest() {
+ mockServer = MockRestServiceServer.createServer(restTemplate);
+ }
+
+ @Test
+ public void whenGetAllCarsThenRetrieveRequestedCars() throws JsonProcessingException {
+ Car expectedOne = new Car(66L, "test");
+ List<Car> expected = new ArrayList<>();
+ expected.add(expectedOne);
+
+ mockServer.expect(requestTo(apiCarsUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
+
+ List<Car> cars = carClientService.doGetCars();
+
+ mockServer.verify();
+
+ assertEquals(1, cars.size());
+ assertEquals(expectedOne, cars.get(0));
+ }
+
+ @Test
+ public void whenGetCarByIdThenRetrieveRequestedCar() throws JsonProcessingException {
+ Long id = 66L;
+ Car expected = new Car(66L, "test");
+
+ mockServer.expect(requestTo(apiCarUrl.replace(":id", String.valueOf(id))))
+ .andExpect(method(HttpMethod.GET))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
+
+ Car car = carClientService.doGetCar(id);
+
+ mockServer.verify();
+
+ assertNotNull(car);
+ assertEquals(expected, car);
+ }
+
+ @Test
+ public void whenCreateNewCarThenRetrieveCreatedCar() throws JsonProcessingException {
+ Long expectedId = 66L;
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.LOCATION, "/api/cars/" + String.valueOf(expectedId));
+ Car expected = new Car(expectedId, "test");
+
+ mockServer.expect(requestTo(apiCarsUrl))
+ .andExpect(method(HttpMethod.POST))
+ .andExpect(content()
+ .string(asJsonString(expected)))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8)
+ .headers(headers));
+
+ Car car = carClientService.doNewCar(expected);
+
+ mockServer.verify();
+
+ assertNotNull(car);
+ assertEquals(expected, car);
+ }
+
+ private String asJsonString(final Object obj) throws JsonProcessingException {
+ return jsonObjectMapperFactory.getObject().writeValueAsString(obj);
+ }
+}
--- /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-rest</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <name>web-services-spring-rest-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-rest</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-server</artifactId>
+ <packaging>war</packaging>
+ <name>web-services-spring-rest-server</name>
+ <url>http://gumartinm.name</url>
+ <dependencies>
+ <dependency>
+ <groupId>de.spring.webservices</groupId>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</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>
+
+ <!-- Creates WADL from Spring REST annotations -->
+ <dependency>
+ <groupId>org.jvnet.ws.wadl</groupId>
+ <artifactId>wadl-core</artifactId>
+ <version>1.1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jvnet.ws.wadl</groupId>
+ <artifactId>wadl-client-plugin</artifactId>
+ <version>1.1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-oxm</artifactId>
+ <version>4.2.4.RELEASE</version>
+ </dependency>
+
+ <!-- API documentation -->
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger1</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ <version>2.3.1</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.doc;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.builders.ResponseMessageBuilder;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger.web.UiConfiguration;
+import springfox.documentation.swagger1.annotations.EnableSwagger;
+
+@Configuration
+@EnableWebMvc
+@EnableSwagger
+@ComponentScan("de.spring.webservices.rest.controller")
+public class Swagger2Configuration {
+
+ @Bean
+ public Docket documentation() {
+ return new Docket(DocumentationType.SWAGGER_12)
+ .select()
+ .apis(RequestHandlerSelectors.withMethodAnnotation(RequestMapping.class))
+ .paths(PathSelectors.any())
+ .build()
+ .globalResponseMessage(RequestMethod.GET,
+ newArrayList(new ResponseMessageBuilder()
+ .code(500).message("Global server custom error message").build()))
+ .pathMapping("/")
+ .useDefaultResponseMessages(false)
+ .apiInfo(metadata())
+ .enable(true);
+ }
+
+ @Bean
+ UiConfiguration uiConfig() {
+ return UiConfiguration.DEFAULT;
+ }
+
+ private static ApiInfo metadata() {
+ return new ApiInfoBuilder()
+ .title("gumartinm REST API")
+ .description("Gustavo Martin Morcuende")
+ .version("1.0-SNAPSHOT")
+ .contact("gumartinm.name")
+ .build();
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.ResponseHeader;
+
+@RestController
+@RequestMapping("/api/cars/")
+public class CarController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CarController.class);
+ private static final String TEMPLATE = "Car: %s";
+
+ private final AtomicLong counter = new AtomicLong();
+
+ @ApiOperation(value = "Get all available cars", nickname = "getAllCars", responseContainer="List", response = Car.class)
+ @ApiResponses({
+ @ApiResponse(code = 404, message ="Specific getCars not found"),
+ @ApiResponse(code = 400, message ="Specific getCars invalid input")
+ })
+ @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
+ @ResponseStatus(HttpStatus.OK)
+ public List<Car> cars() {
+ 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)));
+
+ return cars;
+ }
+
+ @ApiOperation(value = "Get one car", nickname = "getOneCar", response = Car.class)
+ @ApiResponses({
+ @ApiResponse(code = 404, message ="Specific getCar not found"),
+ @ApiResponse(code = 400, message ="Specific getCar invalid input")
+ })
+ @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,
+ @ApiParam(name = "id", value = "Car id", required = true) @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);
+ }
+ }
+
+ try {
+ Thread.sleep(10000); //1000 milliseconds is one second.
+ } catch(InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+
+ return new Car(counter.incrementAndGet(), String.format(TEMPLATE, id));
+ }
+
+ @ApiOperation(code = 201, value = "Create one new car", nickname = "createNewCar")
+ @ApiResponses({
+ @ApiResponse(code = 201, message ="Specific createCar with header",
+ responseHeaders = { @ResponseHeader(name = HttpHeaders.LOCATION) }, response = Car.class),
+ @ApiResponse(code = 404, message ="Specific createCar not found"),
+ @ApiResponse(code = 400, message ="Specific createCar invalid input")
+ })
+ @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) {
+ long count = counter.incrementAndGet();
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.LOCATION, "/api/cars/" + count);
+
+ return new ResponseEntity<>(new Car(count, String.format(TEMPLATE, count)), headers, HttpStatus.CREATED);
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.wadl;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.namespace.QName;
+
+import org.apache.commons.lang.StringUtils;
+import org.jvnet.ws.wadl.Application;
+import org.jvnet.ws.wadl.Doc;
+import org.jvnet.ws.wadl.Param;
+import org.jvnet.ws.wadl.ParamStyle;
+import org.jvnet.ws.wadl.Representation;
+import org.jvnet.ws.wadl.Request;
+import org.jvnet.ws.wadl.Resource;
+import org.jvnet.ws.wadl.Resources;
+import org.jvnet.ws.wadl.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+
+/**
+ * Taken from: http://javattitude.com/2014/05/26/wadl-generator-for-spring-rest/
+ *
+ * With some modifications.
+ *
+ */
+@Controller
+@RequestMapping("/rest.wadl")
+public class WADLController {
+ private static final String NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema" ;
+ private static final String WADL_TITLE = "Spring REST Service WADL";
+
+ private final AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping;
+ private final ApplicationContext context;
+
+ @Autowired
+ public WADLController(AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping, ApplicationContext context) {
+ this.handlerMapping = handlerMapping;
+ this.context = context;
+ }
+
+ @RequestMapping(produces = { MediaType.APPLICATION_XML_VALUE }, method=RequestMethod.GET )
+ public @ResponseBody Application generateWadl(HttpServletRequest request) {
+ Application result = new Application();
+
+ Doc doc = new Doc();
+ doc.setTitle(WADL_TITLE);
+ result.getDoc().add(doc);
+
+ Resources wadlResources = new Resources();
+ wadlResources.setBase(getBaseUrl(request));
+
+ handlerMapping.getHandlerMethods().forEach( (mappingInfo, handlerMethod) -> {
+ Object object = handlerMethod.getBean();
+ Object bean = context.getBean(object.toString());
+ if(!bean.getClass().isAnnotationPresent(RestController.class)) {
+ return;
+ }
+
+ mappingInfo.getMethodsCondition().getMethods().forEach(httpMethod -> {
+ Resource wadlResource = null;
+ org.jvnet.ws.wadl.Method wadlMethod = new org.jvnet.ws.wadl.Method();
+
+ Set<String> pattern = mappingInfo.getPatternsCondition().getPatterns();
+ for (String uri : pattern) {
+ wadlResource = createOrFind(uri, wadlResources);
+ wadlResource.setPath(uri);
+ }
+
+ wadlMethod.setName(httpMethod.name());
+ Method javaMethod = handlerMethod.getMethod();
+ wadlMethod.setId(javaMethod.getName());
+ Doc wadlDocMethod = new Doc();
+ wadlDocMethod.setTitle(javaMethod.getDeclaringClass().getSimpleName() + "." + javaMethod.getName());
+ wadlMethod.getDoc().add(wadlDocMethod);
+
+ // Request
+ Request wadlRequest = new Request();
+ Annotation[][] annotations = javaMethod.getParameterAnnotations();
+ Class<?>[] paramTypes = javaMethod.getParameterTypes();
+ int i = 0;
+ for (Annotation[] annotation : annotations) {
+ Class<?> paramType =paramTypes[i];
+ i++;
+ for (Annotation annotation2 : annotation) {
+
+ Param wadlParam = doParam(annotation2, paramType);
+ if (wadlParam != null) {
+ wadlRequest.getParam().add(wadlParam);
+ }
+ }
+ }
+ if (!wadlRequest.getParam().isEmpty() ) {
+ wadlMethod.setRequest(wadlRequest);
+ }
+
+ // Response
+ Set<MediaType> mediaTypes = mappingInfo.getProducesCondition().getProducibleMediaTypes();
+ if (!mediaTypes.isEmpty()) {
+ ResponseStatus status = handlerMethod.getMethodAnnotation(ResponseStatus.class);
+ Response wadlResponse = doResponse(mediaTypes, status);
+ wadlMethod.getResponse().add(wadlResponse);
+ }
+
+
+ wadlResource.getMethodOrResource().add(wadlMethod);
+ });
+
+ });
+ result.getResources().add(wadlResources);
+
+ return result;
+ }
+
+ private Param doParam(Annotation annotation2, Class<?> paramType) {
+ Param wadlParam = null;
+
+ if (annotation2 instanceof RequestParam ) {
+ RequestParam param = (RequestParam)annotation2;
+
+ wadlParam = new Param();
+ QName nm = convertJavaToXMLType(paramType);
+ if (StringUtils.isNotEmpty(nm.getLocalPart())) {
+ wadlParam.setType(nm);
+ }
+ wadlParam.setName(param.value());
+
+
+ if (!ValueConstants.DEFAULT_NONE.equals(param.defaultValue())) {
+ String defaultValue = cleanDefault(param.defaultValue());
+ if (StringUtils.isNotEmpty(defaultValue) ) {
+ wadlParam.setDefault(defaultValue);
+ }
+ }
+
+ wadlParam.setStyle(ParamStyle.QUERY);
+ wadlParam.setRequired(param.required());
+ } else if (annotation2 instanceof PathVariable ) {
+ PathVariable param = (PathVariable)annotation2;
+
+ wadlParam = new Param();
+ QName nm = convertJavaToXMLType(paramType);
+ if (StringUtils.isNotEmpty(nm.getLocalPart())) {
+ wadlParam.setType(nm);
+ }
+ wadlParam.setName(param.value());
+
+
+ wadlParam.setStyle(ParamStyle.TEMPLATE);
+ wadlParam.setRequired(true);
+ }
+
+ return wadlParam;
+ }
+
+ private Response doResponse(Set<MediaType> mediaTypes, ResponseStatus status) {
+ Response wadlResponse = new Response();
+
+ mediaTypes.forEach(mediaType -> {
+ Representation wadlRepresentation = new Representation();
+ wadlRepresentation.setMediaType(mediaType.toString());
+ wadlResponse.getRepresentation().add(wadlRepresentation);
+ });
+
+ wadlResponse.getStatus().add(getResposeValue(status));
+
+ return wadlResponse;
+ }
+
+ private long getResposeValue(ResponseStatus status) {
+ if(status == null) {
+ return HttpStatus.OK.value();
+ } else {
+ HttpStatus httpcode = status.value();
+ return httpcode.value();
+ }
+ }
+
+ private QName convertJavaToXMLType(Class<?> type) {
+ QName nm = new QName("");
+ String classname = type.toString();
+ classname = classname.toLowerCase();
+
+ if (classname.indexOf("string") >= 0) {
+ nm = new QName(NAMESPACE_URI, "string", "xs");
+ } else if (classname.indexOf("integer") >= 0) {
+ nm = new QName(NAMESPACE_URI, "int", "xs");
+ } else if (classname.indexOf("long") >= 0) {
+ nm = new QName(NAMESPACE_URI, "long", "xs");
+ }
+
+ return nm;
+ }
+
+ private Resource createOrFind(String uri, Resources wadResources) {
+ List<Resource> current = wadResources.getResource();
+ for(Resource resource:current) {
+ if(resource.getPath().equalsIgnoreCase(uri)){
+ return resource;
+ }
+ }
+ Resource wadlResource = new Resource();
+ current.add(wadlResource);
+ return wadlResource;
+ }
+
+ private String getBaseUrl(HttpServletRequest request) {
+ String requestUri = request.getRequestURI();
+ int index = requestUri.lastIndexOf('/');
+ requestUri = requestUri.substring(0, index);
+
+ return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + requestUri;
+ }
+
+ private String cleanDefault(String value) {
+ value = value.replaceAll("\t", "");
+ value = value.replaceAll("\n", "");
+ return value;
+ }
+}
--- /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"?>
+<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">
+
+ <!--
+
+ Local deployment URLs:
+
+ Swagger:
+ http://localhost:8080/web-services-spring-rest-server/api-docs
+
+ Swagger-UI:
+ http://localhost:8080/web-services-spring-rest-server/swagger-ui.html
+
+ -->
+
+ <context:annotation-config />
+
+ <context:component-scan base-package="de.spring.webservices.doc"/>
+
+ <bean class="de.spring.webservices.doc.Swagger2Configuration"/>
+
+</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.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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration({ "classpath*:spring-configuration/mvc/rest/*.xml"})
+public class CarControllerIntegrationTest {
+ private CarController controller;
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ controller = new CarController();
+ mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+ }
+
+ @Test
+ public void testWhenGetAllCarsThenRetrieveJsonValues() throws Exception {
+ 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);
+ }
+}
--- /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-rest</artifactId>
+ <packaging>pom</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <name>web-services-spring-rest</name>
+ <url>http://gumartinm.name</url>
+ <description>Web Services Spring Framework</description>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>http://www.gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:http://git.gumartinm.name/SpringWebServicesForFun</developerConnection>
+ <url>http://git.gumartinm.name/SpringWebServicesForFun</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>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>
+
+ <!-- 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>
\ No newline at end of file
--- /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.example.spring.kafka</groupId>
+ <artifactId>web-services-spring-rest</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>web-services-spring-rest</name>
+ <url>https://gumartinm.name/</url>
+ <description>Web Services Spring REST</description>
+ <organization>
+ <name>gumartinm</name>
+ <url>https://gumartinm.name/</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:https://git.gumartinm.name/SpringWebServicesForFun</developerConnection>
+ <url>https://git.gumartinm.name/SpringWebServicesForFun</url>
+ </scm>
+
+ <modules>
+ <module>web-services-spring-rest-bom</module>
+ <module>web-services-spring-rest-global</module>
+ <module>web-services-spring-rest-server</module>
+ </modules>
+
+</project>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ 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>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>1.5.1.RELEASE</version>
+ </parent>
+
+ <groupId>de.spring.webservices</groupId>
+ <artifactId>web-services-spring-rest-bom</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>web-services-spring-rest-bom</name>
+
+ <url>https://gumartinm.name</url>
+ <description>Web Services Spring REST Framework BOM.</description>
+ <organization>
+ <name>gumartinm</name>
+ <url>https://gumartinm.name</url>
+ </organization>
+ <scm>
+ <developerConnection>scm:git:https://git.gumartinm.name/SpringWebServicesForFun</developerConnection>
+ <url>https://git.gumartinm.name/SpringWebServicesForFun</url>
+ </scm>
+ <properties>
+ <skip.unit.tests>false</skip.unit.tests>
+ <skip.integration.tests>false</skip.integration.tests>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <profiles>
+ <profile>
+ <id>release</id>
+ <properties>
+ <environment.profile>release</environment.profile>
+ </properties>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ </profile>
+ </profiles>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-log4j2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <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>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <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>
+
+ <!-- Used for unit tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- Skips unit tests if the value of skip.unit.tests property is true -->
+ <skipTests>${skip.unit.tests}</skipTests>
+ <!-- Excludes integration tests when unit tests are run. -->
+ <excludes>
+ <exclude>**/*IT.java</exclude>
+ <exclude>**/*IntegrationTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+
+ <!-- Used for integration tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ <configuration>
+ <!-- Skips integration tests if the value of skip.integration.tests
+ property is true -->
+ <skipTests>${skip.integration.tests}</skipTests>
+ <includes>
+ <include>**/*IT.java</include>
+ <include>**/*IntegrationTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ <pluginManagement>
+ <plugins>
+ <!--
+ It will create our running jar file.
+ The main class must be located in: src/main/java/de/example/spring/kafka
+ otherwise you need an explicit declaration using this property
+ <start-class>de.exmaple.spring.kafka.Application</start-class>
+
+ The start-class property is automatically populated by spring-boot-maven-plugin and it is
+ used in spring-boot-starter-parent.
+
+ See: http://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/build-tool-plugins-maven-plugin.html#build-tool-plugins-maven-packaging-optional-params
+ -->
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>1.4.4.RELEASE</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </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-rest</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-client</artifactId>
+ <name>web-services-spring-rest-client</name>
+ <url>http://gumartinm.name</url>
+ <dependencies>
+ <dependency>
+ <groupId>de.spring.webservices</groupId>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </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>
+
+ <!-- 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>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/resources/</directory>
+ <includes>
+ <include>**/*.*</include>
+ </includes>
+ </resource>
+ </resources>
+ </build>
+</project>
\ No newline at end of file
--- /dev/null
+package de.spring.webservices.main;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import de.spring.webservices.rest.business.service.BusinessService;
+
+
+/**
+ * This class is used just like a nice example about how to write and run client
+ * code which will send data to and from the Web Services.
+ *
+ */
+public class MainTest {
+ public ApplicationContext context;
+
+ /**
+ * @param args
+ */
+ public static void main(final String[] args) {
+ final MainTest test = new MainTest();
+
+ test.context = new ClassPathXmlApplicationContext(
+ "classpath:spring-configuration/rest-config.xml");
+
+ final BusinessService example =
+ (BusinessService) test.context.getBean("businessService");
+
+ example.doSomethingWithCars();
+
+ example.doSomethingWithCar(66L);
+
+ example.createsNewCar();
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.business.service;
+
+
+public interface BusinessService {
+
+ public void doSomethingWithCars();
+
+ public void doSomethingWithCar(long id);
+
+ public void createsNewCar();
+}
--- /dev/null
+package de.spring.webservices.rest.business.service.impl;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.BusinessService;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+@Service("businessService")
+public class BusinessServiceImpl implements BusinessService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BusinessServiceImpl.class);
+
+ private final CarClientService carClientService;
+
+ @Autowired
+ public BusinessServiceImpl(CarClientService carClientService) {
+ this.carClientService = carClientService;
+ }
+
+
+ @Override
+ public void doSomethingWithCars() {
+ List<Car> cars = carClientService.doGetCars();
+ LOGGER.info("Retrieved cars");
+ for (Car car : cars) {
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+ }
+
+ @Override
+ public void doSomethingWithCar(long id) {
+ Car car = carClientService.doGetCar(id);
+ LOGGER.info("Retrieved car");
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+
+ @Override
+ public void createsNewCar() {
+ Car newCar = new Car(666L, "just_a_test");
+
+ Car car = carClientService.doNewCar(newCar);
+ LOGGER.info("New car");
+ LOGGER.info("car: " + car.getId());
+ LOGGER.info(car.getContent());
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.client.service;
+
+import java.util.List;
+
+import de.spring.webservices.domain.Car;
+
+public interface CarClientService {
+
+ public List<Car> doGetCars();
+
+ public Car doGetCar(long id);
+
+ public Car doNewCar(Car car);
+}
--- /dev/null
+package de.spring.webservices.rest.client.service.impl;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+@Service("carClientService")
+public class CarClientServiceImpl implements CarClientService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CarClientServiceImpl.class);
+
+ private final String apiCarsUrl;
+ private final String apiCarUrl;
+ private final RestTemplate restTemplate;
+
+ @Autowired
+ public CarClientServiceImpl(@Value("${url.base}${url.cars}") String apiCarsUrl,
+ @Value("${url.base}${url.car}") String apiCarUrl, RestTemplate restTemplate) {
+ this.apiCarsUrl = apiCarsUrl;
+ this.apiCarUrl = apiCarUrl;
+ this.restTemplate = restTemplate;
+ }
+
+
+ @Override
+ public List<Car> doGetCars() {
+ ResponseEntity<Car[]> responseEntity = restTemplate.getForEntity(apiCarsUrl, Car[].class);
+
+ return Arrays.asList(responseEntity.getBody());
+ }
+
+ @Override
+ public Car doGetCar(long id) {
+ ResponseEntity<Car> responseEntity = restTemplate.getForEntity(
+ apiCarUrl.replace(":id", String.valueOf(id)), Car.class);
+
+ return responseEntity.getBody();
+ }
+
+ @Override
+ public Car doNewCar(Car car) {
+ ResponseEntity<Car> responseEntity = restTemplate.postForEntity(apiCarsUrl, car, Car.class);
+ URI newCarLocation = responseEntity.getHeaders().getLocation();
+ LOGGER.info("new car location: " + newCarLocation.getPath());
+
+ return responseEntity.getBody();
+ }
+}
--- /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
+url.base = http://localhost:8080/web-services-spring-rest-server/spring-rest/
+
+url.cars = api/cars/
+url.car = api/cars/:id
\ No newline at end of file
--- /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"
+ 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/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">
+
+
+ <context:annotation-config />
+
+ <context:component-scan base-package="de.spring.webservices.rest"/>
+
+ <context:property-placeholder location="classpath:rest.properties" />
+
+ <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"/>
+ <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
+ </util:list>
+
+ <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
+ <constructor-arg ref="messageConverters" />
+ </bean>
+
+</beans>
--- /dev/null
+package de.spring.webservices.rest.business.service;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import de.spring.webservices.domain.Car;
+import de.spring.webservices.rest.business.service.impl.BusinessServiceImpl;
+import de.spring.webservices.rest.client.service.CarClientService;
+
+public class BusinessServiceTest {
+
+ private CarClientService carClientService;
+ private BusinessService businessService;
+
+ @Before
+ public void createTest() {
+ carClientService = mock(CarClientService.class);
+ businessService = new BusinessServiceImpl(carClientService);
+ }
+
+ @Test
+ public void whenDoSomethingWithCarsThenInvokeDoGetCars() {
+ Car expectedOne = new Car(66L, "test");
+ Car expectedTwo = new Car(99L, "example");
+ List<Car> expected = new ArrayList<>();
+ expected.add(expectedOne);
+ expected.add(expectedTwo);
+ when(carClientService.doGetCars()).thenReturn(expected);
+
+ businessService.doSomethingWithCars();
+
+ verify(carClientService, times(1)).doGetCars();
+ }
+
+ @Test
+ public void whenDoSomethingWithOneCarhenInvokeDoGetCar() {
+ Long id = 66L;
+ Car expected = new Car(66L, "test");
+
+ when(carClientService.doGetCar(id)).thenReturn(expected);
+
+ businessService.doSomethingWithCar(id);
+
+ verify(carClientService, times(1)).doGetCar(id);
+ }
+
+ @Test
+ public void whenCreateNewCarThenCreateNewOne() {
+ Car expected = new Car(66L, "test");
+ ArgumentCaptor<Car> argCar = ArgumentCaptor.forClass(Car.class);
+
+ when(carClientService.doNewCar(argCar.capture())).thenReturn(expected);
+
+ businessService.createsNewCar();
+
+ verify(carClientService, times(1)).doNewCar(argCar.getValue());
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.client.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import de.spring.webservices.domain.Car;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath*:spring-configuration/rest-config.xml")
+public class CarClientServiceIntegrationTest {
+
+ @Value("${url.base}${url.cars}")
+ private String apiCarsUrl;
+
+ @Value("${url.base}${url.car}")
+ private String apiCarUrl;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Autowired
+ private CarClientService carClientService;
+
+ @Autowired
+ private Jackson2ObjectMapperFactoryBean jsonObjectMapperFactory;
+
+ private MockRestServiceServer mockServer;
+
+ @Before
+ public void createTest() {
+ mockServer = MockRestServiceServer.createServer(restTemplate);
+ }
+
+ @Test
+ public void whenGetAllCarsThenRetrieveRequestedCars() throws JsonProcessingException {
+ Car expectedOne = new Car(66L, "test");
+ List<Car> expected = new ArrayList<>();
+ expected.add(expectedOne);
+
+ mockServer.expect(requestTo(apiCarsUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
+
+ List<Car> cars = carClientService.doGetCars();
+
+ mockServer.verify();
+
+ assertEquals(1, cars.size());
+ assertEquals(expectedOne, cars.get(0));
+ }
+
+ @Test
+ public void whenGetCarByIdThenRetrieveRequestedCar() throws JsonProcessingException {
+ Long id = 66L;
+ Car expected = new Car(66L, "test");
+
+ mockServer.expect(requestTo(apiCarUrl.replace(":id", String.valueOf(id))))
+ .andExpect(method(HttpMethod.GET))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
+
+ Car car = carClientService.doGetCar(id);
+
+ mockServer.verify();
+
+ assertNotNull(car);
+ assertEquals(expected, car);
+ }
+
+ @Test
+ public void whenCreateNewCarThenRetrieveCreatedCar() throws JsonProcessingException {
+ Long expectedId = 66L;
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.LOCATION, "/api/cars/" + String.valueOf(expectedId));
+ Car expected = new Car(expectedId, "test");
+
+ mockServer.expect(requestTo(apiCarsUrl))
+ .andExpect(method(HttpMethod.POST))
+ .andExpect(content()
+ .string(asJsonString(expected)))
+ .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8)
+ .headers(headers));
+
+ Car car = carClientService.doNewCar(expected);
+
+ mockServer.verify();
+
+ assertNotNull(car);
+ assertEquals(expected, car);
+ }
+
+ private String asJsonString(final Object obj) throws JsonProcessingException {
+ return jsonObjectMapperFactory.getObject().writeValueAsString(obj);
+ }
+}
--- /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-rest-bom</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <name>web-services-spring-rest-global</name>
+ <url>http://gumartinm.name</url>
+
+ <dependencies>
+
+ <!-- Required for using JSR-303, validations
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>${bv.api.version}</version>
+ </dependency>
+
+ Instead we will rely on the dependencies provided by spring-boot-starter-web.
+ -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+
+
+ </dependencies>
+</project>
--- /dev/null
+package de.spring.webservices.domain;
+
+import java.util.Objects;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+public class Car {
+
+ @NotNull
+ private Long id;
+
+ @NotNull
+ @Size(max = 1024)
+ private String content;
+
+ // Required by Jackson :/
+ protected Car() {
+
+ }
+
+ 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() {
+ return Objects.hash(getId(), getContent());
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof Car)) {
+ return false;
+ }
+
+ final Car other = (Car) object;
+ return Objects.equals(getId(), other.getId())
+ && Objects.equals(getContent(), other.getContent());
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <appender name="CONSOLE_ACCESS" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}" %D "%header{X-Parent-Id}" "%header{X-Trace-Id}"</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ <withJansi>true</withJansi>
+ </appender>
+
+ <appender-ref ref="CONSOLE_ACCESS" />
+</configuration>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true">
+ <property name="FILE_LOG_PATTERN" value="%date{ISO8601,GMT} %level ${PID:- } [%t] %logger : %replace(%msg \{%mdc\} %throwable){'\n','\\\\n'}%n%nopex"/>
+
+ <!--
+ To have color support in IntelliJ, please install 'Grep Console' plugin and set Enable ANSI coloring
+ -->
+ <property name="CONSOLE_LOG_PATTERN" value="%date{ISO8601,GMT} %highlight(${LOG_LEVEL_PATTERN:-%5p}) %magenta(${PID:- }) [%15.15t] %cyan(%-40.40logger{39}) : %m \{%mdc\}%n"/>
+
+
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+ <charset>utf8</charset>
+ </encoder>
+ <withJansi>true</withJansi>
+ </appender>
+
+
+
+ <!-- Here you should define your loggers -->
+
+ <Logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
+ <appender-ref ref="CONSOLE" />
+ </Logger>
+
+ <!--
+ Logging Hibernate QUERY PARAMETERS requires TRACE level.
+ -->
+ <Logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
+ <appender-ref ref="CONSOLE" />
+ </Logger>
+
+
+ <root level="INFO">
+ <appender-ref ref="CONSOLE" />
+ </root>
+</configuration>
--- /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-rest-bom</artifactId>
+ <groupId>de.spring.webservices</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>web-services-spring-rest-server</artifactId>
+ <name>web-services-spring-rest-server</name>
+ <url>http://gumartinm.name</url>
+ <dependencies>
+ <dependency>
+ <groupId>de.spring.webservices</groupId>
+ <artifactId>web-services-spring-rest-global</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <!--
+ Jackson dependency required for serializing and deserializing LocalDateTime,
+ LocalDate, etc, etc objects.
+ -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+
+
+ <!-- Unitary and integration tests -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+
+ <!-- Creates WADL from Spring REST annotations -->
+ <dependency>
+ <groupId>org.jvnet.ws.wadl</groupId>
+ <artifactId>wadl-core</artifactId>
+ <version>1.1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jvnet.ws.wadl</groupId>
+ <artifactId>wadl-client-plugin</artifactId>
+ <version>1.1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-oxm</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+
+
+ <!-- API documentation -->
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <version>2.6.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ <version>2.6.1</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+package de.spring.webservices;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class);
+ }
+
+
+ @Bean
+ CommandLineRunner lookup() {
+ return args -> {
+
+ };
+ }
+
+}
--- /dev/null
+package de.spring.webservices.configuration;
+
+import javax.inject.Inject;
+
+import org.springframework.context.annotation.Configuration;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+@Configuration
+public class JacksonConfiguration {
+
+ @Inject
+ public void configureJackson(ObjectMapper jackson2ObjectMapper) {
+ jackson2ObjectMapper
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .registerModule(new JavaTimeModule());
+ }
+
+}
--- /dev/null
+package de.spring.webservices.configuration;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
+import static springfox.documentation.builders.RequestHandlerSelectors.withMethodAnnotation;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.ResponseMessageBuilder;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger.web.UiConfiguration;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Go to URL: http://localhost:8080/swagger-ui.html#/
+ *
+ */
+@Configuration
+@EnableSwagger2
+public class Swagger2Configuration {
+
+ @Bean
+ public Docket documentation() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(withMethodAnnotation(RequestMapping.class))
+ .apis(basePackage("de.spring.webservices.rest.controller"))
+ .paths(PathSelectors.any())
+ .build()
+ .globalResponseMessage(RequestMethod.GET,
+ newArrayList(new ResponseMessageBuilder()
+ .code(500).message("Global server custom error message").build()))
+ .useDefaultResponseMessages(false)
+ .apiInfo(metadata())
+ .enable(true);
+ }
+
+ @Bean
+ UiConfiguration uiConfig() {
+ return new UiConfiguration(null);
+ }
+
+ private static ApiInfo metadata() {
+ return new ApiInfoBuilder()
+ .title("gumartinm REST API")
+ .description("Gustavo Martin Morcuende")
+ .version("1.0-SNAPSHOT")
+ .contact(doContact())
+ .build();
+ }
+
+ private static Contact doContact() {
+ return new Contact("Gustavo Martin", "https://gumartinm.name", "");
+ }
+}
--- /dev/null
+package de.spring.webservices.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+@Configuration
+public class ValidatorConfiguration {
+
+ @Bean
+ public LocalValidatorFactoryBean localValidatorFactoryBean() {
+ return new LocalValidatorFactoryBean();
+ }
+
+}
--- /dev/null
+package de.spring.webservices.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+
+@Configuration
+public class WadlConfiguration {
+
+ @Bean
+ public Jaxb2Marshaller marshaller() {
+ Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+ marshaller.setPackagesToScan("org.jvnet.ws.wadl");
+ return marshaller;
+ }
+
+ @Bean
+ public MarshallingHttpMessageConverter marshallingHttpMessageConverter(Jaxb2Marshaller marshaller) {
+ return new MarshallingHttpMessageConverter(marshaller);
+ }
+}
--- /dev/null
+package de.spring.webservices.rest.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+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.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import de.spring.webservices.domain.Car;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.ResponseHeader;
+
+@RestController
+@RequestMapping("/api/cars/")
+public class CarController {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CarController.class);
+ private static final String TEMPLATE = "Car: %s";
+
+ @NotNull
+ private final AtomicLong counter = new AtomicLong();
+
+ @ApiOperation(value = "Get all available cars", nickname = "getAllCars", responseContainer="List", response = Car.class)
+ @ApiResponses({
+ @ApiResponse(code = 404, message ="Specific getCars not found"),
+ @ApiResponse(code = 400, message ="Specific getCars invalid input")
+ })
+ @GetMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE })
+ @ResponseStatus(HttpStatus.OK)
+ public List<Car> cars() {
+ 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)));
+
+ return cars;
+ }
+
+ @ApiOperation(value = "Get one car", nickname = "getOneCar", response = Car.class)
+ @ApiResponses({
+ @ApiResponse(code = 404, message ="Specific getCar not found"),
+ @ApiResponse(code = 400, message ="Specific getCar invalid input")
+ })
+ @GetMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ @ResponseStatus(HttpStatus.OK)
+ public Car car(@RequestHeader(value = "MY_HEADER", required = false) String specialHeader,
+ @ApiParam(name = "id", value = "Car id", required = true) @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);
+ }
+ }
+
+ try {
+ Thread.sleep(10000); //1000 milliseconds is one second.
+ } catch(InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+
+ return new Car(counter.incrementAndGet(), String.format(TEMPLATE, id));
+ }
+
+ @ApiOperation(code = 201, value = "Create one new car", nickname = "createNewCar")
+ @ApiResponses({
+ @ApiResponse(code = 201, message ="Specific createCar with header",
+ responseHeaders = { @ResponseHeader(name = HttpHeaders.LOCATION) }, response = Car.class),
+ @ApiResponse(code = 404, message ="Specific createCar not found"),
+ @ApiResponse(code = 400, message ="Specific createCar invalid input")
+ })
+ @PostMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ @ResponseStatus(HttpStatus.CREATED)
+ public ResponseEntity<Car> create(@RequestBody @Valid Car car) {
+ long count = counter.incrementAndGet();
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.LOCATION, "/api/cars/" + count);
+
+ return new ResponseEntity<>(new Car(count, String.format(TEMPLATE, count)), headers, HttpStatus.CREATED);
+ }
+
+}
--- /dev/null
+package de.spring.webservices.rest.wadl;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.namespace.QName;
+
+import org.apache.commons.lang.StringUtils;
+import org.jvnet.ws.wadl.Application;
+import org.jvnet.ws.wadl.Doc;
+import org.jvnet.ws.wadl.Param;
+import org.jvnet.ws.wadl.ParamStyle;
+import org.jvnet.ws.wadl.Representation;
+import org.jvnet.ws.wadl.Request;
+import org.jvnet.ws.wadl.Resource;
+import org.jvnet.ws.wadl.Resources;
+import org.jvnet.ws.wadl.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+
+/**
+ * Taken from: http://javattitude.com/2014/05/26/wadl-generator-for-spring-rest/
+ *
+ * With some modifications.
+ *
+ */
+@Controller
+@RequestMapping("/rest.wadl")
+public class WADLController {
+ private static final String NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema" ;
+ private static final String WADL_TITLE = "Spring REST Service WADL";
+
+ private final AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping;
+ private final ApplicationContext context;
+
+ @Autowired
+ public WADLController(AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping, ApplicationContext context) {
+ this.handlerMapping = handlerMapping;
+ this.context = context;
+ }
+
+ @RequestMapping(produces = { MediaType.APPLICATION_XML_VALUE }, method=RequestMethod.GET )
+ public @ResponseBody Application generateWadl(HttpServletRequest request) {
+ Application result = new Application();
+
+ Doc doc = new Doc();
+ doc.setTitle(WADL_TITLE);
+ result.getDoc().add(doc);
+
+ Resources wadlResources = new Resources();
+ wadlResources.setBase(getBaseUrl(request));
+
+ handlerMapping.getHandlerMethods().forEach( (mappingInfo, handlerMethod) -> {
+ Object object = handlerMethod.getBean();
+ Object bean = context.getBean(object.toString());
+ if(!bean.getClass().isAnnotationPresent(RestController.class)) {
+ return;
+ }
+
+ mappingInfo.getMethodsCondition().getMethods().forEach(httpMethod -> {
+ Resource wadlResource = null;
+ org.jvnet.ws.wadl.Method wadlMethod = new org.jvnet.ws.wadl.Method();
+
+ Set<String> pattern = mappingInfo.getPatternsCondition().getPatterns();
+ for (String uri : pattern) {
+ wadlResource = createOrFind(uri, wadlResources);
+ wadlResource.setPath(uri);
+ }
+
+ wadlMethod.setName(httpMethod.name());
+ Method javaMethod = handlerMethod.getMethod();
+ wadlMethod.setId(javaMethod.getName());
+ Doc wadlDocMethod = new Doc();
+ wadlDocMethod.setTitle(javaMethod.getDeclaringClass().getSimpleName() + "." + javaMethod.getName());
+ wadlMethod.getDoc().add(wadlDocMethod);
+
+ // Request
+ Request wadlRequest = new Request();
+ Annotation[][] annotations = javaMethod.getParameterAnnotations();
+ Class<?>[] paramTypes = javaMethod.getParameterTypes();
+ int i = 0;
+ for (Annotation[] annotation : annotations) {
+ Class<?> paramType =paramTypes[i];
+ i++;
+ for (Annotation annotation2 : annotation) {
+
+ Param wadlParam = doParam(annotation2, paramType);
+ if (wadlParam != null) {
+ wadlRequest.getParam().add(wadlParam);
+ }
+ }
+ }
+ if (!wadlRequest.getParam().isEmpty() ) {
+ wadlMethod.setRequest(wadlRequest);
+ }
+
+ // Response
+ Set<MediaType> mediaTypes = mappingInfo.getProducesCondition().getProducibleMediaTypes();
+ if (!mediaTypes.isEmpty()) {
+ ResponseStatus status = handlerMethod.getMethodAnnotation(ResponseStatus.class);
+ Response wadlResponse = doResponse(mediaTypes, status);
+ wadlMethod.getResponse().add(wadlResponse);
+ }
+
+
+ wadlResource.getMethodOrResource().add(wadlMethod);
+ });
+
+ });
+ result.getResources().add(wadlResources);
+
+ return result;
+ }
+
+ private Param doParam(Annotation annotation2, Class<?> paramType) {
+ Param wadlParam = null;
+
+ if (annotation2 instanceof RequestParam ) {
+ RequestParam param = (RequestParam)annotation2;
+
+ wadlParam = new Param();
+ QName nm = convertJavaToXMLType(paramType);
+ if (StringUtils.isNotEmpty(nm.getLocalPart())) {
+ wadlParam.setType(nm);
+ }
+ wadlParam.setName(param.value());
+
+
+ if (!ValueConstants.DEFAULT_NONE.equals(param.defaultValue())) {
+ String defaultValue = cleanDefault(param.defaultValue());
+ if (StringUtils.isNotEmpty(defaultValue) ) {
+ wadlParam.setDefault(defaultValue);
+ }
+ }
+
+ wadlParam.setStyle(ParamStyle.QUERY);
+ wadlParam.setRequired(param.required());
+ } else if (annotation2 instanceof PathVariable ) {
+ PathVariable param = (PathVariable)annotation2;
+
+ wadlParam = new Param();
+ QName nm = convertJavaToXMLType(paramType);
+ if (StringUtils.isNotEmpty(nm.getLocalPart())) {
+ wadlParam.setType(nm);
+ }
+ wadlParam.setName(param.value());
+
+
+ wadlParam.setStyle(ParamStyle.TEMPLATE);
+ wadlParam.setRequired(true);
+ }
+
+ return wadlParam;
+ }
+
+ private Response doResponse(Set<MediaType> mediaTypes, ResponseStatus status) {
+ Response wadlResponse = new Response();
+
+ mediaTypes.forEach(mediaType -> {
+ Representation wadlRepresentation = new Representation();
+ wadlRepresentation.setMediaType(mediaType.toString());
+ wadlResponse.getRepresentation().add(wadlRepresentation);
+ });
+
+ wadlResponse.getStatus().add(getResposeValue(status));
+
+ return wadlResponse;
+ }
+
+ private long getResposeValue(ResponseStatus status) {
+ if(status == null) {
+ return HttpStatus.OK.value();
+ } else {
+ HttpStatus httpcode = status.value();
+ return httpcode.value();
+ }
+ }
+
+ private QName convertJavaToXMLType(Class<?> type) {
+ QName nm = new QName("");
+ String classname = type.toString();
+ classname = classname.toLowerCase();
+
+ if (classname.indexOf("string") >= 0) {
+ nm = new QName(NAMESPACE_URI, "string", "xs");
+ } else if (classname.indexOf("integer") >= 0) {
+ nm = new QName(NAMESPACE_URI, "int", "xs");
+ } else if (classname.indexOf("long") >= 0) {
+ nm = new QName(NAMESPACE_URI, "long", "xs");
+ }
+
+ return nm;
+ }
+
+ private Resource createOrFind(String uri, Resources wadResources) {
+ List<Resource> current = wadResources.getResource();
+ for(Resource resource:current) {
+ if(resource.getPath().equalsIgnoreCase(uri)){
+ return resource;
+ }
+ }
+ Resource wadlResource = new Resource();
+ current.add(wadlResource);
+ return wadlResource;
+ }
+
+ private String getBaseUrl(HttpServletRequest request) {
+ String requestUri = request.getRequestURI();
+ int index = requestUri.lastIndexOf('/');
+ requestUri = requestUri.substring(0, index);
+
+ return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + requestUri;
+ }
+
+ private String cleanDefault(String value) {
+ value = value.replaceAll("\t", "");
+ value = value.replaceAll("\n", "");
+ return value;
+ }
+}
--- /dev/null
+spring:
+ application:
+ name: web-services-spring-rest-server
+ main:
+ banner-mode: "LOG"
+
+server:
+ port: 8080
+
+springfox.documentation.swagger.v2.path: /schema
+
+logging:
+ config: classpath:log4j2.xml
--- /dev/null
+ _____ _ __ __ _ _____ _
+ / ____| (_) \ \ / / | | / ____| (_)
+ | (___ _ __ _ __ _ _ __ __ _ \ \ /\ / /__| |__ | (___ ___ _ ____ ___ ___ ___ ___
+ \___ \| '_ \| '__| | '_ \ / _` | \ \/ \/ / _ \ '_ \ \___ \ / _ \ '__\ \ / / |/ __/ _ \/ __|
+ ____) | |_) | | | | | | | (_| | \ /\ / __/ |_) | ____) | __/ | \ V /| | (_| __/\__ \
+ |_____/| .__/|_| |_|_| |_|\__, | \/ \/ \___|_.__/ |_____/ \___|_| \_/ |_|\___\___||___/
+ | | __/ |
+ |_| |___/
+ ${application.title} ${application.formatted-version}
\ No newline at end of file
--- /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>
\ No newline at end of file
--- /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.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 javax.inject.Inject;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+//import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import de.spring.webservices.domain.Car;
+
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(CarController.class)
+public class CarControllerIntegrationTest {
+
+ // For injecting and mocking services which could be used in the Controller under test.
+ //@MockBean
+ //private CarService carService;
+
+ @Inject
+ private WebApplicationContext context;
+
+ @Inject
+ private ObjectMapper objectMapper;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(context)
+ .build();
+
+ objectMapper
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .registerModule(new JavaTimeModule());
+ }
+
+ @Test
+ public void testWhenGetAllCarsThenRetrieveJsonValues() throws Exception {
+ 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(objectMapper.writeValueAsString(car))
+ .accept(MediaType.APPLICATION_JSON_UTF8))
+
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("id", any(Integer.class)))
+ .andExpect(jsonPath("content", is("Car: 2")))
+ .andExpect(header().string(HttpHeaders.LOCATION, "/api/cars/2"))
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
+ }
+}
+++ /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-rest</artifactId>
- <groupId>de.spring.webservices</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <artifactId>web-services-spring-rest-client</artifactId>
- <name>web-services-spring-rest-client</name>
- <url>http://gumartinm.name</url>
- <dependencies>
- <dependency>
- <groupId>de.spring.webservices</groupId>
- <artifactId>web-services-spring-rest-global</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- </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>
-
- <!-- 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>
- </dependencies>
- <build>
- <resources>
- <resource>
- <directory>${basedir}/src/main/resources/</directory>
- <includes>
- <include>**/*.*</include>
- </includes>
- </resource>
- </resources>
- </build>
-</project>
\ No newline at end of file
+++ /dev/null
-package de.spring.webservices.main;
-
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-import de.spring.webservices.rest.business.service.BusinessService;
-
-
-/**
- * This class is used just like a nice example about how to write and run client
- * code which will send data to and from the Web Services.
- *
- */
-public class MainTest {
- public ApplicationContext context;
-
- /**
- * @param args
- */
- public static void main(final String[] args) {
- final MainTest test = new MainTest();
-
- test.context = new ClassPathXmlApplicationContext(
- "classpath:spring-configuration/rest-config.xml");
-
- final BusinessService example =
- (BusinessService) test.context.getBean("businessService");
-
- example.doSomethingWithCars();
-
- example.doSomethingWithCar(66L);
-
- example.createsNewCar();
- }
-}
+++ /dev/null
-package de.spring.webservices.rest.business.service;
-
-
-public interface BusinessService {
-
- public void doSomethingWithCars();
-
- public void doSomethingWithCar(long id);
-
- public void createsNewCar();
-}
+++ /dev/null
-package de.spring.webservices.rest.business.service.impl;
-
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import de.spring.webservices.domain.Car;
-import de.spring.webservices.rest.business.service.BusinessService;
-import de.spring.webservices.rest.client.service.CarClientService;
-
-@Service("businessService")
-public class BusinessServiceImpl implements BusinessService {
- private static final Logger LOGGER = LoggerFactory.getLogger(BusinessServiceImpl.class);
-
- private final CarClientService carClientService;
-
- @Autowired
- public BusinessServiceImpl(CarClientService carClientService) {
- this.carClientService = carClientService;
- }
-
-
- @Override
- public void doSomethingWithCars() {
- List<Car> cars = carClientService.doGetCars();
- LOGGER.info("Retrieved cars");
- for (Car car : cars) {
- LOGGER.info("car: " + car.getId());
- LOGGER.info(car.getContent());
- }
- }
-
- @Override
- public void doSomethingWithCar(long id) {
- Car car = carClientService.doGetCar(id);
- LOGGER.info("Retrieved car");
- LOGGER.info("car: " + car.getId());
- LOGGER.info(car.getContent());
- }
-
- @Override
- public void createsNewCar() {
- Car newCar = new Car(666L, "just_a_test");
-
- Car car = carClientService.doNewCar(newCar);
- LOGGER.info("New car");
- LOGGER.info("car: " + car.getId());
- LOGGER.info(car.getContent());
- }
-}
+++ /dev/null
-package de.spring.webservices.rest.client.service;
-
-import java.util.List;
-
-import de.spring.webservices.domain.Car;
-
-public interface CarClientService {
-
- public List<Car> doGetCars();
-
- public Car doGetCar(long id);
-
- public Car doNewCar(Car car);
-}
+++ /dev/null
-package de.spring.webservices.rest.client.service.impl;
-
-import java.net.URI;
-import java.util.Arrays;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import de.spring.webservices.domain.Car;
-import de.spring.webservices.rest.client.service.CarClientService;
-
-@Service("carClientService")
-public class CarClientServiceImpl implements CarClientService {
- private static final Logger LOGGER = LoggerFactory.getLogger(CarClientServiceImpl.class);
-
- private final String apiCarsUrl;
- private final String apiCarUrl;
- private final RestTemplate restTemplate;
-
- @Autowired
- public CarClientServiceImpl(@Value("${url.base}${url.cars}") String apiCarsUrl,
- @Value("${url.base}${url.car}") String apiCarUrl, RestTemplate restTemplate) {
- this.apiCarsUrl = apiCarsUrl;
- this.apiCarUrl = apiCarUrl;
- this.restTemplate = restTemplate;
- }
-
-
- @Override
- public List<Car> doGetCars() {
- ResponseEntity<Car[]> responseEntity = restTemplate.getForEntity(apiCarsUrl, Car[].class);
-
- return Arrays.asList(responseEntity.getBody());
- }
-
- @Override
- public Car doGetCar(long id) {
- ResponseEntity<Car> responseEntity = restTemplate.getForEntity(
- apiCarUrl.replace(":id", String.valueOf(id)), Car.class);
-
- return responseEntity.getBody();
- }
-
- @Override
- public Car doNewCar(Car car) {
- ResponseEntity<Car> responseEntity = restTemplate.postForEntity(apiCarsUrl, car, Car.class);
- URI newCarLocation = responseEntity.getHeaders().getLocation();
- LOGGER.info("new car location: " + newCarLocation.getPath());
-
- return responseEntity.getBody();
- }
-}
+++ /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
-url.base = http://localhost:8080/web-services-spring-rest-server/spring-rest/
-
-url.cars = api/cars/
-url.car = api/cars/:id
\ No newline at end of file
+++ /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"
- 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/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">
-
-
- <context:annotation-config />
-
- <context:component-scan base-package="de.spring.webservices.rest"/>
-
- <context:property-placeholder location="classpath:rest.properties" />
-
- <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"/>
- <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
- </util:list>
-
- <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
- <constructor-arg ref="messageConverters" />
- </bean>
-
-</beans>
+++ /dev/null
-package de.spring.webservices.rest.business.service;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import de.spring.webservices.domain.Car;
-import de.spring.webservices.rest.business.service.impl.BusinessServiceImpl;
-import de.spring.webservices.rest.client.service.CarClientService;
-
-public class BusinessServiceTest {
-
- private CarClientService carClientService;
- private BusinessService businessService;
-
- @Before
- public void createTest() {
- carClientService = mock(CarClientService.class);
- businessService = new BusinessServiceImpl(carClientService);
- }
-
- @Test
- public void whenDoSomethingWithCarsThenInvokeDoGetCars() {
- Car expectedOne = new Car(66L, "test");
- Car expectedTwo = new Car(99L, "example");
- List<Car> expected = new ArrayList<>();
- expected.add(expectedOne);
- expected.add(expectedTwo);
- when(carClientService.doGetCars()).thenReturn(expected);
-
- businessService.doSomethingWithCars();
-
- verify(carClientService, times(1)).doGetCars();
- }
-
- @Test
- public void whenDoSomethingWithOneCarhenInvokeDoGetCar() {
- Long id = 66L;
- Car expected = new Car(66L, "test");
-
- when(carClientService.doGetCar(id)).thenReturn(expected);
-
- businessService.doSomethingWithCar(id);
-
- verify(carClientService, times(1)).doGetCar(id);
- }
-
- @Test
- public void whenCreateNewCarThenCreateNewOne() {
- Car expected = new Car(66L, "test");
- ArgumentCaptor<Car> argCar = ArgumentCaptor.forClass(Car.class);
-
- when(carClientService.doNewCar(argCar.capture())).thenReturn(expected);
-
- businessService.createsNewCar();
-
- verify(carClientService, times(1)).doNewCar(argCar.getValue());
- }
-}
+++ /dev/null
-package de.spring.webservices.rest.client.service;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
-import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.MediaType;
-import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.web.client.MockRestServiceServer;
-import org.springframework.web.client.RestTemplate;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-
-import de.spring.webservices.domain.Car;
-
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath*:spring-configuration/rest-config.xml")
-public class CarClientServiceIntegrationTest {
-
- @Value("${url.base}${url.cars}")
- private String apiCarsUrl;
-
- @Value("${url.base}${url.car}")
- private String apiCarUrl;
-
- @Autowired
- private RestTemplate restTemplate;
-
- @Autowired
- private CarClientService carClientService;
-
- @Autowired
- private Jackson2ObjectMapperFactoryBean jsonObjectMapperFactory;
-
- private MockRestServiceServer mockServer;
-
- @Before
- public void createTest() {
- mockServer = MockRestServiceServer.createServer(restTemplate);
- }
-
- @Test
- public void whenGetAllCarsThenRetrieveRequestedCars() throws JsonProcessingException {
- Car expectedOne = new Car(66L, "test");
- List<Car> expected = new ArrayList<>();
- expected.add(expectedOne);
-
- mockServer.expect(requestTo(apiCarsUrl))
- .andExpect(method(HttpMethod.GET))
- .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
-
- List<Car> cars = carClientService.doGetCars();
-
- mockServer.verify();
-
- assertEquals(1, cars.size());
- assertEquals(expectedOne, cars.get(0));
- }
-
- @Test
- public void whenGetCarByIdThenRetrieveRequestedCar() throws JsonProcessingException {
- Long id = 66L;
- Car expected = new Car(66L, "test");
-
- mockServer.expect(requestTo(apiCarUrl.replace(":id", String.valueOf(id))))
- .andExpect(method(HttpMethod.GET))
- .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8));
-
- Car car = carClientService.doGetCar(id);
-
- mockServer.verify();
-
- assertNotNull(car);
- assertEquals(expected, car);
- }
-
- @Test
- public void whenCreateNewCarThenRetrieveCreatedCar() throws JsonProcessingException {
- Long expectedId = 66L;
- HttpHeaders headers = new HttpHeaders();
- headers.add(HttpHeaders.LOCATION, "/api/cars/" + String.valueOf(expectedId));
- Car expected = new Car(expectedId, "test");
-
- mockServer.expect(requestTo(apiCarsUrl))
- .andExpect(method(HttpMethod.POST))
- .andExpect(content()
- .string(asJsonString(expected)))
- .andRespond(withSuccess(asJsonString(expected), MediaType.APPLICATION_JSON_UTF8)
- .headers(headers));
-
- Car car = carClientService.doNewCar(expected);
-
- mockServer.verify();
-
- assertNotNull(car);
- assertEquals(expected, car);
- }
-
- private String asJsonString(final Object obj) throws JsonProcessingException {
- return jsonObjectMapperFactory.getObject().writeValueAsString(obj);
- }
-}
+++ /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-rest</artifactId>
- <groupId>de.spring.webservices</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <artifactId>web-services-spring-rest-global</artifactId>
- <name>web-services-spring-rest-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-rest</artifactId>
- <groupId>de.spring.webservices</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <artifactId>web-services-spring-rest-server</artifactId>
- <packaging>war</packaging>
- <name>web-services-spring-rest-server</name>
- <url>http://gumartinm.name</url>
- <dependencies>
- <dependency>
- <groupId>de.spring.webservices</groupId>
- <artifactId>web-services-spring-rest-global</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</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>
-
- <!-- Creates WADL from Spring REST annotations -->
- <dependency>
- <groupId>org.jvnet.ws.wadl</groupId>
- <artifactId>wadl-core</artifactId>
- <version>1.1.6</version>
- </dependency>
- <dependency>
- <groupId>org.jvnet.ws.wadl</groupId>
- <artifactId>wadl-client-plugin</artifactId>
- <version>1.1.6</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-oxm</artifactId>
- <version>4.2.4.RELEASE</version>
- </dependency>
-
- <!-- API documentation -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger1</artifactId>
- <version>2.3.1</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.3.1</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.doc;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.builders.ResponseMessageBuilder;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger.web.UiConfiguration;
-import springfox.documentation.swagger1.annotations.EnableSwagger;
-
-@Configuration
-@EnableWebMvc
-@EnableSwagger
-@ComponentScan("de.spring.webservices.rest.controller")
-public class Swagger2Configuration {
-
- @Bean
- public Docket documentation() {
- return new Docket(DocumentationType.SWAGGER_12)
- .select()
- .apis(RequestHandlerSelectors.withMethodAnnotation(RequestMapping.class))
- .paths(PathSelectors.any())
- .build()
- .globalResponseMessage(RequestMethod.GET,
- newArrayList(new ResponseMessageBuilder()
- .code(500).message("Global server custom error message").build()))
- .pathMapping("/")
- .useDefaultResponseMessages(false)
- .apiInfo(metadata())
- .enable(true);
- }
-
- @Bean
- UiConfiguration uiConfig() {
- return UiConfiguration.DEFAULT;
- }
-
- private static ApiInfo metadata() {
- return new ApiInfoBuilder()
- .title("gumartinm REST API")
- .description("Gustavo Martin Morcuende")
- .version("1.0-SNAPSHOT")
- .contact("gumartinm.name")
- .build();
- }
-
-}
+++ /dev/null
-package de.spring.webservices.rest.controller;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-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 io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
-import io.swagger.annotations.ResponseHeader;
-
-@RestController
-@RequestMapping("/api/cars/")
-public class CarController {
- private static final Logger LOGGER = LoggerFactory.getLogger(CarController.class);
- private static final String TEMPLATE = "Car: %s";
-
- private final AtomicLong counter = new AtomicLong();
-
- @ApiOperation(value = "Get all available cars", nickname = "getAllCars", responseContainer="List", response = Car.class)
- @ApiResponses({
- @ApiResponse(code = 404, message ="Specific getCars not found"),
- @ApiResponse(code = 400, message ="Specific getCars invalid input")
- })
- @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET)
- @ResponseStatus(HttpStatus.OK)
- public List<Car> cars() {
- 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)));
-
- return cars;
- }
-
- @ApiOperation(value = "Get one car", nickname = "getOneCar", response = Car.class)
- @ApiResponses({
- @ApiResponse(code = 404, message ="Specific getCar not found"),
- @ApiResponse(code = 400, message ="Specific getCar invalid input")
- })
- @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,
- @ApiParam(name = "id", value = "Car id", required = true) @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);
- }
- }
-
- try {
- Thread.sleep(10000); //1000 milliseconds is one second.
- } catch(InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
-
-
- return new Car(counter.incrementAndGet(), String.format(TEMPLATE, id));
- }
-
- @ApiOperation(code = 201, value = "Create one new car", nickname = "createNewCar")
- @ApiResponses({
- @ApiResponse(code = 201, message ="Specific createCar with header",
- responseHeaders = { @ResponseHeader(name = HttpHeaders.LOCATION) }, response = Car.class),
- @ApiResponse(code = 404, message ="Specific createCar not found"),
- @ApiResponse(code = 400, message ="Specific createCar invalid input")
- })
- @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) {
- long count = counter.incrementAndGet();
- HttpHeaders headers = new HttpHeaders();
- headers.add(HttpHeaders.LOCATION, "/api/cars/" + count);
-
- return new ResponseEntity<>(new Car(count, String.format(TEMPLATE, count)), headers, HttpStatus.CREATED);
- }
-
-}
+++ /dev/null
-package de.spring.webservices.rest.wadl;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.xml.namespace.QName;
-
-import org.apache.commons.lang.StringUtils;
-import org.jvnet.ws.wadl.Application;
-import org.jvnet.ws.wadl.Doc;
-import org.jvnet.ws.wadl.Param;
-import org.jvnet.ws.wadl.ParamStyle;
-import org.jvnet.ws.wadl.Representation;
-import org.jvnet.ws.wadl.Request;
-import org.jvnet.ws.wadl.Resource;
-import org.jvnet.ws.wadl.Resources;
-import org.jvnet.ws.wadl.Response;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.ValueConstants;
-import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
-
-/**
- * Taken from: http://javattitude.com/2014/05/26/wadl-generator-for-spring-rest/
- *
- * With some modifications.
- *
- */
-@Controller
-@RequestMapping("/rest.wadl")
-public class WADLController {
- private static final String NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema" ;
- private static final String WADL_TITLE = "Spring REST Service WADL";
-
- private final AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping;
- private final ApplicationContext context;
-
- @Autowired
- public WADLController(AbstractHandlerMethodMapping<RequestMappingInfo> handlerMapping, ApplicationContext context) {
- this.handlerMapping = handlerMapping;
- this.context = context;
- }
-
- @RequestMapping(produces = { MediaType.APPLICATION_XML_VALUE }, method=RequestMethod.GET )
- public @ResponseBody Application generateWadl(HttpServletRequest request) {
- Application result = new Application();
-
- Doc doc = new Doc();
- doc.setTitle(WADL_TITLE);
- result.getDoc().add(doc);
-
- Resources wadlResources = new Resources();
- wadlResources.setBase(getBaseUrl(request));
-
- handlerMapping.getHandlerMethods().forEach( (mappingInfo, handlerMethod) -> {
- Object object = handlerMethod.getBean();
- Object bean = context.getBean(object.toString());
- if(!bean.getClass().isAnnotationPresent(RestController.class)) {
- return;
- }
-
- mappingInfo.getMethodsCondition().getMethods().forEach(httpMethod -> {
- Resource wadlResource = null;
- org.jvnet.ws.wadl.Method wadlMethod = new org.jvnet.ws.wadl.Method();
-
- Set<String> pattern = mappingInfo.getPatternsCondition().getPatterns();
- for (String uri : pattern) {
- wadlResource = createOrFind(uri, wadlResources);
- wadlResource.setPath(uri);
- }
-
- wadlMethod.setName(httpMethod.name());
- Method javaMethod = handlerMethod.getMethod();
- wadlMethod.setId(javaMethod.getName());
- Doc wadlDocMethod = new Doc();
- wadlDocMethod.setTitle(javaMethod.getDeclaringClass().getSimpleName() + "." + javaMethod.getName());
- wadlMethod.getDoc().add(wadlDocMethod);
-
- // Request
- Request wadlRequest = new Request();
- Annotation[][] annotations = javaMethod.getParameterAnnotations();
- Class<?>[] paramTypes = javaMethod.getParameterTypes();
- int i = 0;
- for (Annotation[] annotation : annotations) {
- Class<?> paramType =paramTypes[i];
- i++;
- for (Annotation annotation2 : annotation) {
-
- Param wadlParam = doParam(annotation2, paramType);
- if (wadlParam != null) {
- wadlRequest.getParam().add(wadlParam);
- }
- }
- }
- if (!wadlRequest.getParam().isEmpty() ) {
- wadlMethod.setRequest(wadlRequest);
- }
-
- // Response
- Set<MediaType> mediaTypes = mappingInfo.getProducesCondition().getProducibleMediaTypes();
- if (!mediaTypes.isEmpty()) {
- ResponseStatus status = handlerMethod.getMethodAnnotation(ResponseStatus.class);
- Response wadlResponse = doResponse(mediaTypes, status);
- wadlMethod.getResponse().add(wadlResponse);
- }
-
-
- wadlResource.getMethodOrResource().add(wadlMethod);
- });
-
- });
- result.getResources().add(wadlResources);
-
- return result;
- }
-
- private Param doParam(Annotation annotation2, Class<?> paramType) {
- Param wadlParam = null;
-
- if (annotation2 instanceof RequestParam ) {
- RequestParam param = (RequestParam)annotation2;
-
- wadlParam = new Param();
- QName nm = convertJavaToXMLType(paramType);
- if (StringUtils.isNotEmpty(nm.getLocalPart())) {
- wadlParam.setType(nm);
- }
- wadlParam.setName(param.value());
-
-
- if (!ValueConstants.DEFAULT_NONE.equals(param.defaultValue())) {
- String defaultValue = cleanDefault(param.defaultValue());
- if (StringUtils.isNotEmpty(defaultValue) ) {
- wadlParam.setDefault(defaultValue);
- }
- }
-
- wadlParam.setStyle(ParamStyle.QUERY);
- wadlParam.setRequired(param.required());
- } else if (annotation2 instanceof PathVariable ) {
- PathVariable param = (PathVariable)annotation2;
-
- wadlParam = new Param();
- QName nm = convertJavaToXMLType(paramType);
- if (StringUtils.isNotEmpty(nm.getLocalPart())) {
- wadlParam.setType(nm);
- }
- wadlParam.setName(param.value());
-
-
- wadlParam.setStyle(ParamStyle.TEMPLATE);
- wadlParam.setRequired(true);
- }
-
- return wadlParam;
- }
-
- private Response doResponse(Set<MediaType> mediaTypes, ResponseStatus status) {
- Response wadlResponse = new Response();
-
- mediaTypes.forEach(mediaType -> {
- Representation wadlRepresentation = new Representation();
- wadlRepresentation.setMediaType(mediaType.toString());
- wadlResponse.getRepresentation().add(wadlRepresentation);
- });
-
- wadlResponse.getStatus().add(getResposeValue(status));
-
- return wadlResponse;
- }
-
- private long getResposeValue(ResponseStatus status) {
- if(status == null) {
- return HttpStatus.OK.value();
- } else {
- HttpStatus httpcode = status.value();
- return httpcode.value();
- }
- }
-
- private QName convertJavaToXMLType(Class<?> type) {
- QName nm = new QName("");
- String classname = type.toString();
- classname = classname.toLowerCase();
-
- if (classname.indexOf("string") >= 0) {
- nm = new QName(NAMESPACE_URI, "string", "xs");
- } else if (classname.indexOf("integer") >= 0) {
- nm = new QName(NAMESPACE_URI, "int", "xs");
- } else if (classname.indexOf("long") >= 0) {
- nm = new QName(NAMESPACE_URI, "long", "xs");
- }
-
- return nm;
- }
-
- private Resource createOrFind(String uri, Resources wadResources) {
- List<Resource> current = wadResources.getResource();
- for(Resource resource:current) {
- if(resource.getPath().equalsIgnoreCase(uri)){
- return resource;
- }
- }
- Resource wadlResource = new Resource();
- current.add(wadlResource);
- return wadlResource;
- }
-
- private String getBaseUrl(HttpServletRequest request) {
- String requestUri = request.getRequestURI();
- int index = requestUri.lastIndexOf('/');
- requestUri = requestUri.substring(0, index);
-
- return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + requestUri;
- }
-
- private String cleanDefault(String value) {
- value = value.replaceAll("\t", "");
- value = value.replaceAll("\n", "");
- return value;
- }
-}
+++ /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"?>
-<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">
-
- <!--
-
- Local deployment URLs:
-
- Swagger:
- http://localhost:8080/web-services-spring-rest-server/api-docs
-
- Swagger-UI:
- http://localhost:8080/web-services-spring-rest-server/swagger-ui.html
-
- -->
-
- <context:annotation-config />
-
- <context:component-scan base-package="de.spring.webservices.doc"/>
-
- <bean class="de.spring.webservices.doc.Swagger2Configuration"/>
-
-</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.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 org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-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;
-
-
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration({ "classpath*:spring-configuration/mvc/rest/*.xml"})
-public class CarControllerIntegrationTest {
- private CarController controller;
- private MockMvc mockMvc;
-
- @Before
- public void setup() {
- controller = new CarController();
- mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
- }
-
- @Test
- public void testWhenGetAllCarsThenRetrieveJsonValues() throws Exception {
- 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);
- }
-}
+++ /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-rest</artifactId>
- <packaging>pom</packaging>
- <version>1.0-SNAPSHOT</version>
- <name>web-services-spring-rest</name>
- <url>http://gumartinm.name</url>
- <description>Web Services Spring Framework</description>
- <organization>
- <name>Gustavo Martin Morcuende</name>
- <url>http://www.gumartinm.name</url>
- </organization>
- <scm>
- <developerConnection>scm:git:http://git.gumartinm.name/SpringWebServicesForFun</developerConnection>
- <url>http://git.gumartinm.name/SpringWebServicesForFun</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>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>
-
- <!-- 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>
\ No newline at end of file