From: Gustavo Martin Morcuende Date: Sun, 24 Jan 2016 23:07:57 +0000 (+0100) Subject: REST: springfox improvements, WADL generation X-Git-Url: https://git.gumartinm.name/?a=commitdiff_plain;h=83c26b35a2af4d080e6df3bc07f6a89eabaa8d09;p=SpringWebServicesForFun%2F.git REST: springfox improvements, WADL generation springfox responseContainer="List" doesn't work for me :( --- diff --git a/REST/web-services-spring-rest-server/pom.xml b/REST/web-services-spring-rest-server/pom.xml index cff3af6..144a67c 100644 --- a/REST/web-services-spring-rest-server/pom.xml +++ b/REST/web-services-spring-rest-server/pom.xml @@ -38,6 +38,35 @@ com.fasterxml.jackson.core jackson-databind + + + + org.jvnet.ws.wadl + wadl-core + 1.1.6 + + + org.jvnet.ws.wadl + wadl-client-plugin + 1.1.6 + + + org.springframework + spring-oxm + 4.2.4.RELEASE + + + + + io.springfox + springfox-swagger1 + 2.3.1 + + + io.springfox + springfox-swagger-ui + 2.3.1 + diff --git a/REST/web-services-spring-rest-server/src/doc/main/java/de/spring/webservices/rest/doc/Swagger2Configuration.java b/REST/web-services-spring-rest-server/src/doc/main/java/de/spring/webservices/rest/doc/Swagger2Configuration.java deleted file mode 100644 index 6e0de24..0000000 --- a/REST/web-services-spring-rest-server/src/doc/main/java/de/spring/webservices/rest/doc/Swagger2Configuration.java +++ /dev/null @@ -1,52 +0,0 @@ -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(); - } - -} diff --git a/REST/web-services-spring-rest-server/src/doc/main/resources/spring-configuration/spring-config.xml b/REST/web-services-spring-rest-server/src/doc/main/resources/spring-configuration/spring-config.xml deleted file mode 100644 index 5ca5917..0000000 --- a/REST/web-services-spring-rest-server/src/doc/main/resources/spring-configuration/spring-config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - diff --git a/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/doc/Swagger2Configuration.java b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/doc/Swagger2Configuration.java new file mode 100644 index 0000000..df71590 --- /dev/null +++ b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/doc/Swagger2Configuration.java @@ -0,0 +1,58 @@ +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(); + } + +} diff --git a/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/controller/CarController.java b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/controller/CarController.java index e769bc6..c81ca23 100644 --- a/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/controller/CarController.java +++ b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/controller/CarController.java @@ -5,6 +5,8 @@ import java.util.List; 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; @@ -21,10 +23,11 @@ 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.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/") @@ -34,12 +37,11 @@ public class CarController { 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 cars() { @@ -51,16 +53,15 @@ public class CarController { 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 params, @RequestParam(value = "wheel", required = false) String[] wheelParams) { @@ -92,14 +93,16 @@ public class CarController { 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 create(@RequestBody Car car) { long count = counter.incrementAndGet(); HttpHeaders headers = new HttpHeaders(); diff --git a/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/wadl/WADLController.java b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/wadl/WADLController.java new file mode 100644 index 0000000..1125157 --- /dev/null +++ b/REST/web-services-spring-rest-server/src/main/java/de/spring/webservices/rest/wadl/WADLController.java @@ -0,0 +1,236 @@ +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 handlerMapping; + private final ApplicationContext context; + + @Autowired + public WADLController(AbstractHandlerMethodMapping 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 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 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 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 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; + } +} diff --git a/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml b/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml index bed7947..54b8540 100644 --- a/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml +++ b/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/mvc/rest/rest-config.xml @@ -21,8 +21,23 @@ - - + + + + + + + + + + + @@ -30,9 +45,10 @@ - + + diff --git a/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/spring-doc-config.xml b/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/spring-doc-config.xml new file mode 100644 index 0000000..773e14f --- /dev/null +++ b/REST/web-services-spring-rest-server/src/main/resources/spring-configuration/spring-doc-config.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/REST/web-services-spring-rest-server/src/main/webapp/WEB-INF/web.xml b/REST/web-services-spring-rest-server/src/main/webapp/WEB-INF/web.xml index 26089bd..6c9c1d0 100644 --- a/REST/web-services-spring-rest-server/src/main/webapp/WEB-INF/web.xml +++ b/REST/web-services-spring-rest-server/src/main/webapp/WEB-INF/web.xml @@ -34,7 +34,8 @@ spring-rest - /spring-rest/* + + /* diff --git a/REST/web-services-spring-rest/pom.xml b/REST/web-services-spring-rest/pom.xml index bbad4e9..e8ce3dc 100644 --- a/REST/web-services-spring-rest/pom.xml +++ b/REST/web-services-spring-rest/pom.xml @@ -31,59 +31,6 @@ true - - documentation - - documentation - - - false - - - - - io.springfox - springfox-swagger2 - 2.3.1 - - - io.springfox - springfox-swagger-ui - 2.3.1 - - - - - - ${basedir}/src/doc/main/resources/ - - **/*.* - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.10 - - - add-source - process-sources - - add-source - - - - src/doc/main/java/ - - - - - - - -