Skip to content

Commit b5fe7e5

Browse files
author
Ole Lensmar
committed
Merge pull request #1136 from swagger-api/merging-issue-1085
Merge branch 'issue-1085'
2 parents 910ceb9 + f651a6d commit b5fe7e5

File tree

4 files changed

+244
-2
lines changed

4 files changed

+244
-2
lines changed

modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/Reader.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import com.fasterxml.jackson.databind.JavaType;
2020
import com.fasterxml.jackson.databind.type.TypeFactory;
21+
import com.google.common.collect.Collections2;
2122
import io.swagger.annotations.Api;
2223
import io.swagger.annotations.ApiImplicitParam;
2324
import io.swagger.annotations.ApiImplicitParams;
2425
import io.swagger.annotations.ApiOperation;
26+
import io.swagger.annotations.ApiParam;
2527
import io.swagger.annotations.ApiResponse;
2628
import io.swagger.annotations.ApiResponses;
2729
import io.swagger.annotations.Authorization;
@@ -68,11 +70,14 @@
6870
import javax.ws.rs.HttpMethod;
6971
import javax.ws.rs.Produces;
7072
import java.lang.annotation.Annotation;
73+
import java.lang.reflect.Constructor;
74+
import java.lang.reflect.Field;
7175
import java.lang.reflect.Method;
7276
import java.lang.reflect.ParameterizedType;
7377
import java.lang.reflect.Type;
7478
import java.util.ArrayList;
7579
import java.util.Arrays;
80+
import java.util.Collection;
7681
import java.util.Collections;
7782
import java.util.EnumSet;
7883
import java.util.HashMap;
@@ -83,14 +88,36 @@
8388
import java.util.Map;
8489
import java.util.Set;
8590

91+
import javax.ws.rs.Consumes;
92+
import javax.ws.rs.HeaderParam;
93+
import javax.ws.rs.HttpMethod;
94+
import javax.ws.rs.PathParam;
95+
import javax.ws.rs.Produces;
96+
import javax.ws.rs.QueryParam;
97+
98+
import org.apache.commons.lang3.StringUtils;
99+
import org.slf4j.Logger;
100+
import org.slf4j.LoggerFactory;
101+
86102
public class Reader {
87103
private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);
88104
private static final String SUCCESSFUL_OPERATION = "successful operation";
89105
private static final String PATH_DELIMITER = "/";
90106

107+
private static final Set<Class<? extends Annotation>> FIELD_ANNOTATIONS;
91108
private final ReaderConfig config;
92109
private Swagger swagger;
93110

111+
static {
112+
final Set<Class<? extends Annotation>> fieldAnnotations = new HashSet<Class<? extends Annotation>>();
113+
fieldAnnotations.add(PathParam.class);
114+
fieldAnnotations.add(QueryParam.class);
115+
fieldAnnotations.add(HeaderParam.class);
116+
fieldAnnotations.add(ApiParam.class);
117+
fieldAnnotations.add(ApiImplicitParam.class);
118+
FIELD_ANNOTATIONS = Collections.unmodifiableSet(fieldAnnotations);
119+
}
120+
94121
public Reader(Swagger swagger) {
95122
this(swagger, null);
96123
}
@@ -270,8 +297,8 @@ protected Swagger read(Class<?> cls, String parentPath, String parentMethod, boo
270297
final ApiOperation apiOperation = getAnnotation(method, ApiOperation.class);
271298
String httpMethod = extractOperationMethod(apiOperation, method, SwaggerExtensions.chain());
272299

273-
Operation operation = parseMethod(method);
274-
if(operation == null)
300+
Operation operation = parseMethod(method, collectGlobalParameters(cls));
301+
if(operation == null)
275302
continue;
276303
if(parentParameters != null) {
277304
for(Parameter param : parentParameters) {
@@ -668,6 +695,10 @@ public Map<String, Property> parseResponseHeaders(ResponseHeader[] headers) {
668695
}
669696

670697
public Operation parseMethod(Method method) {
698+
return parseMethod(method, Collections.<Parameter> emptyList());
699+
}
700+
701+
private Operation parseMethod(Method method, List<Parameter> globalParameters) {
671702
Operation operation = new Operation();
672703

673704
ApiOperation apiOperation = getAnnotation(method, ApiOperation.class);
@@ -851,6 +882,10 @@ else if(responseType != null && !isVoid(responseType)) {
851882
hidden = apiOperation.hidden();
852883

853884
// process parameters
885+
for (Parameter globalParameter : globalParameters) {
886+
operation.parameter(globalParameter);
887+
}
888+
854889
Type[] genericParameterTypes = method.getGenericParameterTypes();
855890
Annotation[][] paramAnnotations = method.getParameterAnnotations();
856891
for(int i = 0; i < genericParameterTypes.length; i++) {
@@ -1005,6 +1040,31 @@ private static boolean isResourceClass(Class<?> cls) {
10051040
return cls.getAnnotation(Api.class) != null;
10061041
}
10071042

1043+
private List<Parameter> collectGlobalParameters(Class<?> cls) {
1044+
final List<Parameter> globalParameters = new ArrayList<Parameter>();
1045+
1046+
// look for constructor-level annotated properties
1047+
final Constructor<?> constructor = ReflectionUtils.findConstructor(cls);
1048+
if (constructor != null) {
1049+
final Type[] genericParameterTypes = constructor.getGenericParameterTypes();
1050+
final Annotation[][] annotations = constructor.getParameterAnnotations();
1051+
for (int i = 0; i < genericParameterTypes.length; i++) {
1052+
globalParameters.addAll(getParameters(genericParameterTypes[i], Arrays.asList(annotations[i])));
1053+
}
1054+
}
1055+
1056+
// look for field-level annotated properties
1057+
for (Field field : cls.getDeclaredFields()) {
1058+
final List<Annotation> annotations = Arrays.asList(field.getAnnotations());
1059+
final Collection<Class<? extends Annotation>> types = Collections2.transform(annotations, ReflectionUtils.createAnnotationTypeGetter());
1060+
if (!Collections.disjoint(types, FIELD_ANNOTATIONS)) {
1061+
globalParameters.addAll(getParameters(field.getGenericType(), annotations));
1062+
}
1063+
}
1064+
1065+
return globalParameters;
1066+
}
1067+
10081068
enum ContainerWrapper {
10091069
LIST("list") {
10101070
@Override

modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/utils/ReflectionUtils.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
package io.swagger.jaxrs.utils;
22

3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Constructor;
35
import java.lang.reflect.Method;
6+
import java.lang.reflect.Modifier;
47
import java.lang.reflect.Type;
58
import java.util.Arrays;
9+
import java.util.Collection;
10+
import java.util.Collections;
11+
import java.util.HashSet;
12+
import java.util.Set;
13+
14+
import javax.ws.rs.HeaderParam;
15+
import javax.ws.rs.PathParam;
16+
import javax.ws.rs.QueryParam;
17+
18+
import com.google.common.base.Function;
19+
import com.google.common.collect.Collections2;
620

721
public class ReflectionUtils {
822

23+
private static final Set<Class<? extends Annotation>> CONSTRUCTOR_ANNOTATIONS;
24+
25+
static {
26+
final Set<Class<? extends Annotation>> constructorAnnotations = new HashSet<Class<? extends Annotation>>();
27+
constructorAnnotations.add(PathParam.class);
28+
constructorAnnotations.add(QueryParam.class);
29+
constructorAnnotations.add(HeaderParam.class);
30+
CONSTRUCTOR_ANNOTATIONS = Collections.unmodifiableSet(constructorAnnotations);
31+
}
32+
933
/**
1034
* Checks if the method methodToFind is the overridden method from the superclass.
1135
*
@@ -83,4 +107,80 @@ public static Method findMethod(Method methodToFind, Class<?> cls) {
83107
}
84108
return null;
85109
}
110+
111+
/**
112+
* Searches for constructor suitable for resource instantiation.
113+
* <p/>
114+
* If more constructors exists the one with the most injectable parameters will be selected.
115+
*
116+
* @param cls is the class where to search
117+
* @return the suitable constructor
118+
*/
119+
public static Constructor<?> findConstructor(Class<?> cls) {
120+
if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) {
121+
return null;
122+
}
123+
124+
Constructor<?> selected = null;
125+
int selectedCount = 0;
126+
int maxParams = -1;
127+
for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
128+
final Class<?>[] parameterTypes = constructor.getParameterTypes();
129+
if (parameterTypes.length >= maxParams && isCompatible(constructor)) {
130+
if (parameterTypes.length > maxParams) {
131+
maxParams = parameterTypes.length;
132+
selectedCount = 0;
133+
}
134+
135+
selected = constructor;
136+
selectedCount++;
137+
}
138+
}
139+
140+
return selectedCount == 1 ? selected : null;
141+
}
142+
143+
/**
144+
* Returns an implementation of {@link Function} for getting annotation types.
145+
* @return the implementation of {@link Function} for getting annotation types
146+
*/
147+
public static Function<Annotation, Class<? extends Annotation>> createAnnotationTypeGetter() {
148+
return new Function<Annotation, Class<? extends Annotation>>() {
149+
@Override
150+
public Class<? extends Annotation> apply(Annotation annotation) {
151+
return annotation.annotationType();
152+
}
153+
};
154+
}
155+
156+
/**
157+
* Checks if the passed constructor is suitable for resource instantiation.
158+
* Repeats the logic of the {@link org.glassfish.jersey.internal.inject.JerseyClassAnalyzer#isCompatible(java.lang.reflect.Constructor)}
159+
* @param constructor the constructor to be checked
160+
* @return true if the constructor is suitable or false otherwise
161+
*/
162+
private static boolean isCompatible(Constructor<?> constructor) {
163+
for (Annotation annotation : constructor.getAnnotations()) {
164+
// use string name to avoid additional dependencies
165+
if ("javax.inject.Inject".equals(annotation.annotationType().getName())) {
166+
return true;
167+
}
168+
}
169+
170+
if (!Modifier.isPublic(constructor.getModifiers())) {
171+
final int access = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
172+
return constructor.getParameterTypes().length == 0 &&
173+
(constructor.getDeclaringClass().getModifiers() & access) == constructor.getModifiers();
174+
}
175+
176+
for (Annotation[] paramAnnotations : constructor.getParameterAnnotations()) {
177+
final Collection<Class<? extends Annotation>> tmp = Collections2.transform(Arrays.asList(paramAnnotations),
178+
ReflectionUtils.createAnnotationTypeGetter());
179+
if (Collections.disjoint(tmp, CONSTRUCTOR_ANNOTATIONS)) {
180+
return false;
181+
}
182+
}
183+
184+
return true;
185+
}
86186
}

modules/swagger-jaxrs/src/test/scala/ReaderTest.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,24 @@ class ReaderTest extends FlatSpec with Matchers {
8181
swagger.getPaths().get("/{id}/value").getPut.getProduces.get(0) should equal ("text/plain")
8282
}
8383

84+
it should "scan class level and field level annotations" in {
85+
val swagger = new Reader(new Swagger()).read(classOf[ResourceWithKnownInjections])
86+
87+
val resourceParameters = swagger.getPaths().get("/resource/{id}").getGet().getParameters()
88+
resourceParameters should not equal (null)
89+
resourceParameters.size() should equal (3)
90+
resourceParameters.get(0).asInstanceOf[PathParameter].getName() should equal ("id")
91+
resourceParameters.get(1).asInstanceOf[QueryParameter].getName() should equal ("fieldParam")
92+
resourceParameters.get(2).asInstanceOf[QueryParameter].getName() should equal ("methodParam")
93+
94+
val subResourceParameters = swagger.getPaths().get("/resource/{id}/subresource").getGet().getParameters()
95+
subResourceParameters should not equal (null)
96+
subResourceParameters.size() should equal (3)
97+
subResourceParameters.get(0).asInstanceOf[PathParameter].getName() should equal ("id")
98+
subResourceParameters.get(1).asInstanceOf[QueryParameter].getName() should equal ("fieldParam")
99+
subResourceParameters.get(2).asInstanceOf[QueryParameter].getName() should equal ("subResourceParam")
100+
}
101+
84102
def isValidRestPath(method: Method) = {
85103
if(Set(Option(method.getAnnotation(classOf[GET])),
86104
Option(method.getAnnotation(classOf[PUT])),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package resources;
2+
3+
import io.swagger.annotations.Api;
4+
5+
import javax.ws.rs.GET;
6+
import javax.ws.rs.Path;
7+
import javax.ws.rs.PathParam;
8+
import javax.ws.rs.Produces;
9+
import javax.ws.rs.QueryParam;
10+
import javax.ws.rs.core.Context;
11+
import javax.ws.rs.core.Request;
12+
13+
@Path("/resource/{id}")
14+
@Api(value = "/resource", description = "Summary of injections resource")
15+
@Produces({"application/json", "application/xml"})
16+
public class ResourceWithKnownInjections {
17+
18+
private Integer constructorParam;
19+
@QueryParam("fieldParam")
20+
private String fieldParam; // injection into a class field
21+
22+
// injection into a constructor parameter
23+
public ResourceWithKnownInjections(@PathParam("id") Integer constructorParam) {
24+
this.constructorParam = constructorParam;
25+
}
26+
27+
@GET
28+
public String get(@QueryParam("methodParam") String methodParam) {
29+
// injection into a resource method parameter
30+
final StringBuilder sb = new StringBuilder();
31+
sb.append("Constructor param: ").append(constructorParam).append("\n");
32+
sb.append("Field param: ").append(fieldParam).append("\n");
33+
sb.append("Method param: ").append(methodParam).append("\n");
34+
return sb.toString();
35+
}
36+
37+
@Path("/subresource")
38+
public SubResource subResourceLocator(@QueryParam("subResourceParam") String subResourceParam) {
39+
// injection into a sub resource locator parameter
40+
return new SubResource(subResourceParam);
41+
}
42+
43+
@Context
44+
public void setRequest(Request request) {
45+
// injection into a setter method
46+
}
47+
48+
@Api(description = "Sub resource")
49+
public static class SubResource {
50+
51+
private String subResourceParam;
52+
53+
public SubResource(String subResourceParam) {
54+
this.subResourceParam = subResourceParam;
55+
}
56+
57+
@GET
58+
public String get() {
59+
final StringBuilder sb = new StringBuilder();
60+
sb.append("Sub Resource: ").append(subResourceParam);
61+
return sb.toString();
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)