<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>
+++ /dev/null
-package de.spring.webservices.rest.doc;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.bind.annotation.RequestMapping;
-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.service.ApiInfo;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger.web.UiConfiguration;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-@Configuration
-@EnableWebMvc
-@EnableSwagger2
-public class Swagger2Configuration {
- private static final String DOCKET_ID = "web-services-spring-rest";
-
- @Bean
- public Docket documentation() {
- return new Docket(DocumentationType.SWAGGER_2)
- .groupName(DOCKET_ID)
- .select()
- .apis(RequestHandlerSelectors.withMethodAnnotation(RequestMapping.class))
- .paths(PathSelectors.any())
- .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
-<?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/spring-rest/v2/api-docs?group=web-services-spring-rest
-
- Swagger-UI:
- http://localhost:8080/web-services-spring-rest/swagger-ui.html
-
- -->
-
- <bean class="de.spring.webservices.rest.doc.Swagger2Configuration"/>
-
-</beans>
--- /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();
+ }
+
+}
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+import javax.ws.rs.Path;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RestController;
import de.spring.webservices.domain.Car;
-
-//import io.swagger.annotations.ApiOperation;
-//import io.swagger.annotations.ApiResponse;
-//import io.swagger.annotations.ApiResponses;
+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/")
private final AtomicLong counter = new AtomicLong();
-// Do I want to release with Swagger dependencies?
-// @ApiOperation(value = "getCars", nickname = "getAllCars", response = Car.class)
-// @ApiResponses({
-// @ApiResponse(code = 404, message ="Not found"),
-// @ApiResponse(code = 400, message ="Invalid input")
-// })
+ @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() {
return cars;
}
-// Do I want to release with Swagger dependencies?
-// @ApiOperation(value = "getCar", nickname = "getsOneCar", response = Car.class)
-// @ApiResponses({
-// @ApiResponse(code = 404, message ="Not found"),
-// @ApiResponse(code = 400, message ="Invalid input")
-// })
+ @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,
- @PathVariable("id") long id,
+ @ApiParam(name = "id", value = "Car id", required = true) @PathVariable("id") long id,
@RequestParam Map<String, String> params,
@RequestParam(value = "wheel", required = false) String[] wheelParams) {
return new Car(counter.incrementAndGet(), String.format(TEMPLATE, id));
}
-// Do I want to release with Swagger dependencies?
-// @ApiOperation(value = "postCat", nickname = "createsNewCar", response = Car.class)
-// @ApiResponses({
-// @ApiResponse(code = 404, message ="Not found"),
-// @ApiResponse(code = 400, message ="Invalid input")
-// })
+ @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();
--- /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;
+ }
+}
<context:annotation-config />
<context:component-scan base-package="de.spring.webservices.rest"/>
-
- <bean id="jsonObjectMapperFactory" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentOutput="true" p:failOnEmptyBeans="false">
+
+ <!--
+ 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"/>
</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>
--- /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>
<servlet-mapping>
<servlet-name>spring-rest</servlet-name>
- <url-pattern>/spring-rest/*</url-pattern>
+ <!-- REQUIRED PATTERN BY swagger-ui. IT DOESN'T WORK WITH ANY OTHER o.O -->
+ <url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
- <profile>
- <id>documentation</id>
- <properties>
- <environment.profile>documentation</environment.profile>
- </properties>
- <activation>
- <activeByDefault>false</activeByDefault>
- </activation>
- <dependencies>
- <!-- API documentation -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.3.1</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.3.1</version>
- </dependency>
- </dependencies>
- <build>
- <resources>
- <resource>
- <directory>${basedir}/src/doc/main/resources/</directory>
- <includes>
- <include>**/*.*</include>
- </includes>
- </resource>
- </resources>
- <plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <version>1.10</version>
- <executions>
- <execution>
- <id>add-source</id>
- <phase>process-sources</phase>
- <goals>
- <goal>add-source</goal>
- </goals>
- <configuration>
- <sources>
- <source>src/doc/main/java/</source>
- </sources>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
</profiles>
<dependencies>
<!-- 1/3 Required dependency for log4j 2 with slf4j: binding between log4j
<version>2.6.4</version>
</dependency>
+
<!-- Required by spring-context for using JSR-303. See LocalValidatorFactoryBean
in rest-config.xml -->
<dependency>