Skip to content

Instantly share code, notes, and snippets.

@sverhagen
Created August 16, 2015 09:24
Show Gist options
  • Select an option

  • Save sverhagen/2256edf0d5e363480c30 to your computer and use it in GitHub Desktop.

Select an option

Save sverhagen/2256edf0d5e363480c30 to your computer and use it in GitHub Desktop.
Springfox issue #454
package com.totaalsoftware.common.controller.swagger;
import io.swagger.annotations.Api;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Strategy that takes the {@link Api} annotation as a source. It falls back
* on {@link SimpleResourceExtractionStrategy} when incomplete
*/
@Component
@Order(100)
class ApiResourceExtractionStrategy extends SimpleResourceExtractionStrategy {
@Override
public boolean isApplicable(Class<?> controllerClass) {
return AnnotationUtils.findAnnotation(controllerClass, Api.class) != null;
}
@Override
public String getDescription(Class<?> controllerClass) {
Api apiAnnotation = AnnotationUtils.findAnnotation(controllerClass, Api.class);
return isNotBlank(apiAnnotation.description()) ? apiAnnotation.description()
: isNotBlank(apiAnnotation.value()) ? apiAnnotation.value() : super
.getDescription(controllerClass);
}
@Override
public String getGroup(Class<?> controllerClass) {
Api apiAnnotation = AnnotationUtils.findAnnotation(controllerClass, Api.class);
return isBlank(apiAnnotation.value()) ? super.getGroup(controllerClass) : apiAnnotation
.value().toLowerCase().replace(" ", "-");
}
@Override
public Integer getPosition(Class<?> controllerClass) {
Api apiAnnotation = AnnotationUtils.findAnnotation(controllerClass, Api.class);
return apiAnnotation.position();
}
}
package com.totaalsoftware.common.controller.swagger;
import io.swagger.annotations.Api;
import org.junit.Test;
import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link ApiResourceExtractionStrategy}.
*/
public class ApiResourceExtractionStrategyTest {
private ApiResourceExtractionStrategy subject = new ApiResourceExtractionStrategy();
private interface Diversion {
}
@Api(value = "testcontrol", description = "Test Controller Description", position = 42)
private interface TestControllerInterface {
}
private class TestControllerImpl implements Diversion, TestControllerInterface {
}
@Api // no attributes, so will fall back on simple strategy
private class Test2ControllerImpl {
}
private class SomeOtherTestController {
}
@Test
public void testIsApplicable() {
assertTrue(subject.isApplicable(TestControllerImpl.class));
assertTrue(subject.isApplicable(Test2ControllerImpl.class));
assertFalse(subject.isApplicable(SomeOtherTestController.class));
}
@Test
public void testGetDescription() {
assertEquals("Test Controller Description", subject.getDescription(TestControllerImpl.class));
assertEquals("Test 2 Controller Impl", subject.getDescription(Test2ControllerImpl.class));
}
@Test
public void testGetGroup() {
assertEquals("testcontrol", subject.getGroup(TestControllerImpl.class));
assertEquals("test-2-controller-impl", subject.getGroup(Test2ControllerImpl.class));
}
@Test
public void testGetPosition() {
assertEquals(valueOf(42), subject.getPosition(TestControllerImpl.class));
assertEquals(valueOf(0), subject.getPosition(Test2ControllerImpl.class));
}
}
package com.totaalsoftware.common.controller.swagger;
import static com.google.common.collect.Sets.newHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import springfox.documentation.service.ResourceGroup;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ResourceGroupingStrategy;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
/**
* Custom implementation of the {@link ResourceGroupingStrategy} that takes
* care of numerous different ways to extract stuff from a resource (controller
* class).
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomResourceGrouping implements ResourceGroupingStrategy {
/**
* List of resource extraction strategies. These are ordered in the
* preference that we want them to be used. The most-preferable resource
* extraction strategy goes first
*/
private List<ResourceExtractionStrategy> strategies;
private ResourceExtractionStrategy getStrategy(Class<?> controllerClass) {
for (ResourceExtractionStrategy strategy : strategies) {
if (strategy.isApplicable(controllerClass)) {
return strategy;
}
}
// should not reach this ever due to:
// SimpleResourceExtractionStrategy.isApplicable == true
return throwNoResourceExtractionStrategyException(controllerClass);
}
private ResourceExtractionStrategy throwNoResourceExtractionStrategyException(Class<?> controllerClass) {
String message = "no resource extraction strategy for controller class: " + controllerClass.getName();
throw new IllegalArgumentException(message);
}
@Override
public String getResourceDescription(RequestMappingInfo requestMappingInfo,
HandlerMethod handlerMethod) {
Class<?> controllerClass = handlerMethod.getBeanType();
return getStrategy(controllerClass).getDescription(controllerClass);
}
@Override
public Integer getResourcePosition(RequestMappingInfo requestMappingInfo,
HandlerMethod handlerMethod) {
Class<?> controllerClass = handlerMethod.getBeanType();
return getStrategy(controllerClass).getPosition(controllerClass);
}
@Override
public Set<ResourceGroup> getResourceGroups(RequestMappingInfo requestMappingInfo,
HandlerMethod handlerMethod) {
Class<?> controllerClass = handlerMethod.getBeanType();
ResourceExtractionStrategy strategy = getStrategy(controllerClass);
String group = strategy.getGroup(controllerClass);
Integer position = strategy.getPosition(controllerClass);
return newHashSet(new ResourceGroup(group, controllerClass, position));
}
@Override
public boolean supports(DocumentationType documentationType) {
return SwaggerPluginSupport.pluginDoesApply(documentationType);
}
@Autowired
public void setStrategies(List<ResourceExtractionStrategy> strategies) {
this.strategies = strategies;
}
}
package com.totaalsoftware.common.controller.swagger;
import org.junit.Test;
import org.springframework.web.method.HandlerMethod;
import springfox.documentation.service.ResourceGroup;
import springfox.documentation.spi.DocumentationType;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for {@link CustomResourceGrouping}.
*/
public class CustomResourceGroupingTest {
public static final String SOME_DESCRIPTION = "Some Description";
public static final String SOME_GROUP = "some-group";
private static final Integer SOME_POSITION = 42;
private class TestController {
}
@Test
public void testGetDescription() {
ResourceExtractionStrategy strategy1 = strategy(false);
ResourceExtractionStrategy strategy2 = strategy(true);
when(strategy2.getDescription(TestController.class)).thenReturn(SOME_DESCRIPTION);
CustomResourceGrouping subject = getSubject(strategy1, strategy2);
assertEquals(SOME_DESCRIPTION, subject.getResourceDescription(null, handlerMethod(TestController.class)));
}
@Test
public void testGetDescriptionOnFirstStrategy() {
ResourceExtractionStrategy strategy1 = strategy(true);
when(strategy1.getDescription(TestController.class)).thenReturn(SOME_DESCRIPTION);
ResourceExtractionStrategy strategy2 = strategy(true);
CustomResourceGrouping subject = getSubject(strategy1, strategy2);
assertEquals(SOME_DESCRIPTION, subject.getResourceDescription(null, handlerMethod(TestController.class)));
verify(strategy2, never()).isApplicable(TestController.class);
}
@Test
public void testNoApplicableStrategy() {
ResourceExtractionStrategy strategy1 = strategy(false);
ResourceExtractionStrategy strategy2 = strategy(false);
try {
CustomResourceGrouping subject = getSubject(strategy1, strategy2);
subject.getResourceDescription(null, handlerMethod(TestController.class));
fail("expected exception when no strategies can handle the controller class");
}
catch(IllegalArgumentException exception) {
assertTrue(exception.getMessage().startsWith("no resource extraction strategy for controller class"));
}
}
@Test
public void testGetGroup() {
ResourceExtractionStrategy strategy1 = strategy(false);
ResourceExtractionStrategy strategy2 = strategy(true);
when(strategy2.getGroup(TestController.class)).thenReturn(SOME_GROUP);
CustomResourceGrouping subject = getSubject(strategy1, strategy2);
List<ResourceGroup> resourceGroups = new ArrayList<>(subject.getResourceGroups(null, handlerMethod(TestController.class)));
assertEquals(1, resourceGroups.size());
assertEquals(SOME_GROUP, resourceGroups.get(0).getGroupName());
}
@Test
public void testGetPosition() {
ResourceExtractionStrategy strategy1 = strategy(false);
ResourceExtractionStrategy strategy2 = strategy(true);
when(strategy2.getPosition(TestController.class)).thenReturn(SOME_POSITION);
CustomResourceGrouping subject = getSubject(strategy1, strategy2);
assertEquals(SOME_POSITION, subject.getResourcePosition(null, handlerMethod(TestController.class)));
}
@Test
public void testSupports() {
CustomResourceGrouping subject = new CustomResourceGrouping();
assertFalse(subject.supports(DocumentationType.SPRING_WEB));
assertTrue(subject.supports(DocumentationType.SWAGGER_12));
assertTrue(subject.supports(DocumentationType.SWAGGER_2));
}
private <T> HandlerMethod handlerMethod(Class<T> clazz) {
HandlerMethod handlerMethod = mock(HandlerMethod.class);
when((Class<T>) handlerMethod.getBeanType()).thenReturn(clazz);
return handlerMethod;
}
private CustomResourceGrouping getSubject(ResourceExtractionStrategy... strategies) {
CustomResourceGrouping subject = new CustomResourceGrouping();
subject.setStrategies(asList(strategies));
return subject;
}
private ResourceExtractionStrategy strategy(boolean applicable) {
ResourceExtractionStrategy strategy1 = mock(ResourceExtractionStrategy.class);
when(strategy1.isApplicable(TestController.class)).thenReturn(applicable);
return strategy1;
}
}
package com.totaalsoftware.common.controller.swagger;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static springfox.documentation.spring.web.paths.Paths.splitCamelCase;
/**
* Strategy that takes the {@link RequestMapping} annotation as a source,
* plus the class in the hierarchy of the controller class on which the
* {@link RequestMapping} annotation was found.
*/
@Component
@Order(200)
class RequestMappingOnClassResourceExtractionStrategy implements
ResourceExtractionStrategy {
@Override
public boolean isApplicable(Class<?> controllerClass) {
return AnnotationUtils.findAnnotationDeclaringClass(RequestMapping.class,
controllerClass) != null;
}
@Override
public String getDescription(Class<?> controllerClass) {
Class<?> requestMappingClass = AnnotationUtils.findAnnotationDeclaringClass(
RequestMapping.class, controllerClass);
return splitCamelCase(requestMappingClass.getSimpleName(), " ");
}
@Override
public String getGroup(Class<?> controllerClass) {
Class<?> requestMappingClass = AnnotationUtils.findAnnotationDeclaringClass(
RequestMapping.class, controllerClass);
RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
if (requestMapping.value().length > 0) {
String group = requestMapping.value()[0].replaceAll("^/", "").replace("/", "-");
if (isNotBlank(group)) {
return group;
}
}
return splitCamelCase(requestMappingClass.getSimpleName(), "-").toLowerCase();
}
@Override
public Integer getPosition(Class<?> controllerClass) {
return 0;
}
}
package com.totaalsoftware.common.controller.swagger;
import org.junit.Test;
import org.springframework.web.bind.annotation.RequestMapping;
import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link RequestMappingOnClassResourceExtractionStrategy}.
*/
public class RequestMappingOnClassResourceExtractionStrategyTest {
private RequestMappingOnClassResourceExtractionStrategy subject = new RequestMappingOnClassResourceExtractionStrategy();
@RequestMapping
private class TestControllerParent {
}
private class TestController extends TestControllerParent {
}
@RequestMapping("testpath")
private class Test2Controller {
}
private class SomeOtherTestController {
}
@Test
public void testIsApplicable() {
assertTrue(subject.isApplicable(TestController.class));
assertTrue(subject.isApplicable(Test2Controller.class));
assertFalse(subject.isApplicable(SomeOtherTestController.class));
}
@Test
public void testGetDescription() {
assertEquals("Test Controller Parent", subject.getDescription(TestController.class));
assertEquals("Test 2 Controller", subject.getDescription(Test2Controller.class));
}
@Test
public void testGetGroup() {
assertEquals("test-controller-parent", subject.getGroup(TestController.class));
assertEquals("testpath", subject.getGroup(Test2Controller.class));
}
@Test
public void testGetPosition() {
assertEquals(valueOf(0), subject.getPosition(null));
}
}
package com.totaalsoftware.common.controller.swagger;
import org.apache.commons.lang3.ClassUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* Strategy that takes the {@link RequestMapping} annotation as a source,
* plus the interface in the hierarchy of the controller class on which the
* {@link RequestMapping} annotation was found. Unlike
* {@link RequestMappingOnClassResourceExtractionStrategy} it searches
* interfaces in the hierarchy, not just super classes
*/
@Component
@Order(300)
class RequestMappingOnInterfaceResourceExtractionStrategy extends
RequestMappingOnClassResourceExtractionStrategy {
private Class<?> getInterfaceWithRequestMapping(Class<?> controllerClass) {
List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(controllerClass);
for (Class<?> oneInterface : allInterfaces) {
if (oneInterface.isAnnotationPresent(RequestMapping.class)) {
return oneInterface;
}
}
return null;
}
@Override
public boolean isApplicable(Class<?> controllerClass) {
return getInterfaceWithRequestMapping(controllerClass) != null;
}
@Override
public String getDescription(Class<?> controllerClass) {
Class<?> oneInterface = getInterfaceWithRequestMapping(controllerClass);
return super.getDescription(oneInterface);
}
@Override
public String getGroup(Class<?> controllerClass) {
Class<?> oneInterface = getInterfaceWithRequestMapping(controllerClass);
return super.getGroup(oneInterface);
}
@Override
public Integer getPosition(Class<?> controllerClass) {
return 0;
}
}
package com.totaalsoftware.common.controller.swagger;
import org.junit.Test;
import org.springframework.web.bind.annotation.RequestMapping;
import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class RequestMappingOnInterfaceResourceExtractionStrategyTest {
private RequestMappingOnInterfaceResourceExtractionStrategy subject = new RequestMappingOnInterfaceResourceExtractionStrategy();
private interface Diversion {
}
@RequestMapping
private interface TestControllerInterface {
}
private class TestControllerImpl implements Diversion, TestControllerInterface {
}
private class SomeOtherTestController {
}
@Test
public void testIsApplicable() {
assertTrue(subject.isApplicable(TestControllerImpl.class));
assertFalse(subject.isApplicable(SomeOtherTestController.class));
}
@Test
public void testGetDescription() {
assertEquals("Test Controller Interface", subject.getDescription(TestControllerImpl.class));
}
@Test
public void testGetGroup() {
assertEquals("test-controller-interface", subject.getGroup(TestControllerImpl.class));
}
@Test
public void testGetPosition() {
assertEquals(valueOf(0), subject.getPosition(null));
}
}
package com.totaalsoftware.common.controller.swagger;
/**
* Strategy that can extract stuff from a resource (controller class).
*/
public interface ResourceExtractionStrategy {
/**
* Whether this strategy is applicable to the given controller class. For example, if the strategy evolves around
* evaluating a certain annotation, it is not applicable to controller classes without said annotation
*
* @param controllerClass controller class to evaluate
* @return whether this strategy is applicable
*/
boolean isApplicable(Class<?> controllerClass);
/**
* Get description of the given controller class.
*
* @param controllerClass controller class to evaluate
* @return description of controller class
*/
String getDescription(Class<?> controllerClass);
/**
* Get group of the given controller class.
*
* @param controllerClass controller class to evaluate
* @return group of controller class
*/
String getGroup(Class<?> controllerClass);
/**
* Get position of the given controller class.
*
* @param controllerClass controller class to evaluate
* @return position of controller class
*/
Integer getPosition(Class<?> controllerClass);
}
package com.totaalsoftware.common.controller.swagger;
import org.junit.Test;
import org.springframework.core.annotation.Order;
import static org.junit.Assert.assertTrue;
/**
* Test to verify the order of the {@link ResourceExtractionStrategy}s. This is done with Spring's {@link Order}
* annotation
*/
public class ResourceExtractionStrategyOrderTest {
@Test
public void testOrderOfStrategies() {
assertTrue(order(ApiResourceExtractionStrategy.class) < order(RequestMappingOnClassResourceExtractionStrategy.class));
assertTrue(order(RequestMappingOnClassResourceExtractionStrategy.class) < order(RequestMappingOnInterfaceResourceExtractionStrategy.class));
assertTrue(order(RequestMappingOnInterfaceResourceExtractionStrategy.class) < order(SimpleResourceExtractionStrategy.class));
}
private int order(Class<? extends ResourceExtractionStrategy> strategy) {
assertTrue(strategy.isAnnotationPresent(Order.class));
Order order = strategy.getAnnotation(Order.class);
return order.value();
}
}
package com.totaalsoftware.common.controller.swagger;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import static springfox.documentation.spring.web.paths.Paths.splitCamelCase;
/**
* Simple strategy that just takes the controller class as a source.
*/
@Component
@Order(400)
class SimpleResourceExtractionStrategy implements ResourceExtractionStrategy {
@Override
public boolean isApplicable(Class<?> controllerClass) {
return true;
}
@Override
public String getDescription(Class<?> controllerClass) {
return splitCamelCase(controllerClass.getSimpleName(), " ");
}
@Override
public String getGroup(Class<?> controllerClass) {
return splitCamelCase(controllerClass.getSimpleName(), "-").toLowerCase();
}
@Override
public Integer getPosition(Class<?> controllerClass) {
return 0;
}
}
package com.totaalsoftware.common.controller.swagger;
import org.junit.Test;
import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link SimpleResourceExtractionStrategy}.
*/
public class SimpleResourceExtractionStrategyTest {
private SimpleResourceExtractionStrategy subject = new SimpleResourceExtractionStrategy();
private class TestController {
}
@Test
public void testIsApplicable() {
assertTrue(subject.isApplicable(null));
}
@Test
public void testGetDescription() {
assertEquals("Test Controller", subject.getDescription(TestController.class));
}
@Test
public void testGetGroup() {
assertEquals("test-controller", subject.getGroup(TestController.class));
}
@Test
public void testGetPosition() {
assertEquals(valueOf(0), subject.getPosition(null));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment