Skip to content

Commit 8399740

Browse files
committed
Fixed #1191: Parameter annotations from the Jersey2 should be supported in constructor(field)-level injections
1 parent 769b810 commit 8399740

File tree

7 files changed

+269
-135
lines changed

7 files changed

+269
-135
lines changed

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

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@
1616

1717
package io.swagger.jaxrs;
1818

19-
import com.fasterxml.jackson.databind.JavaType;
20-
import com.fasterxml.jackson.databind.type.TypeFactory;
21-
import com.google.common.collect.Collections2;
22-
2319
import io.swagger.annotations.Api;
2420
import io.swagger.annotations.ApiImplicitParam;
2521
import io.swagger.annotations.ApiImplicitParams;
2622
import io.swagger.annotations.ApiOperation;
27-
import io.swagger.annotations.ApiParam;
2823
import io.swagger.annotations.ApiResponse;
2924
import io.swagger.annotations.ApiResponses;
3025
import io.swagger.annotations.Authorization;
@@ -40,6 +35,7 @@
4035
import io.swagger.jaxrs.config.ReaderListener;
4136
import io.swagger.jaxrs.ext.SwaggerExtension;
4237
import io.swagger.jaxrs.ext.SwaggerExtensions;
38+
import io.swagger.jaxrs.utils.ReaderUtils;
4339
import io.swagger.jaxrs.utils.ReflectionUtils;
4440
import io.swagger.models.Contact;
4541
import io.swagger.models.ExternalDocs;
@@ -63,26 +59,18 @@
6359
import io.swagger.models.properties.Property;
6460
import io.swagger.models.properties.RefProperty;
6561

62+
import com.fasterxml.jackson.databind.JavaType;
63+
import com.fasterxml.jackson.databind.type.TypeFactory;
6664
import org.apache.commons.lang3.StringUtils;
6765
import org.slf4j.Logger;
6866
import org.slf4j.LoggerFactory;
6967

70-
import javax.ws.rs.Consumes;
71-
import javax.ws.rs.HeaderParam;
72-
import javax.ws.rs.HttpMethod;
73-
import javax.ws.rs.PathParam;
74-
import javax.ws.rs.Produces;
75-
import javax.ws.rs.QueryParam;
76-
7768
import java.lang.annotation.Annotation;
78-
import java.lang.reflect.Constructor;
79-
import java.lang.reflect.Field;
8069
import java.lang.reflect.Method;
8170
import java.lang.reflect.ParameterizedType;
8271
import java.lang.reflect.Type;
8372
import java.util.ArrayList;
8473
import java.util.Arrays;
85-
import java.util.Collection;
8674
import java.util.Collections;
8775
import java.util.EnumSet;
8876
import java.util.HashMap;
@@ -93,12 +81,15 @@
9381
import java.util.Map;
9482
import java.util.Set;
9583

84+
import javax.ws.rs.Consumes;
85+
import javax.ws.rs.HttpMethod;
86+
import javax.ws.rs.Produces;
87+
9688
public class Reader {
9789
private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);
9890
private static final String SUCCESSFUL_OPERATION = "successful operation";
9991
private static final String PATH_DELIMITER = "/";
10092

101-
private static final Set<Class<? extends Annotation>> FIELD_ANNOTATIONS;
10293
private final ReaderConfig config;
10394
private Swagger swagger;
10495

@@ -242,6 +233,14 @@ protected Swagger read(Class<?> cls, String parentPath, String parentMethod, boo
242233

243234
// handle sub-resources by looking at return type
244235

236+
final List<Parameter> globalParameters = new ArrayList<Parameter>();
237+
238+
// look for constructor-level annotated properties
239+
globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, swagger));
240+
241+
// look for field-level annotated properties
242+
globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, swagger));
243+
245244
// parse the method
246245
final javax.ws.rs.Path apiPath = cls.getAnnotation(javax.ws.rs.Path.class);
247246
Method methods[] = cls.getMethods();
@@ -288,7 +287,7 @@ protected Swagger read(Class<?> cls, String parentPath, String parentMethod, boo
288287
final ApiOperation apiOperation = getAnnotation(method, ApiOperation.class);
289288
String httpMethod = extractOperationMethod(apiOperation, method, SwaggerExtensions.chain());
290289

291-
Operation operation = parseMethod(method, collectGlobalParameters(cls));
290+
Operation operation = parseMethod(method, globalParameters);
292291
if (operation == null) {
293292
continue;
294293
}
@@ -978,30 +977,7 @@ private boolean isIgnored(String path) {
978977
return !javax.ws.rs.core.Response.class.isAssignableFrom(cls) && !isResourceClass(cls);
979978
}
980979

981-
private List<Parameter> collectGlobalParameters(Class<?> cls) {
982-
final List<Parameter> globalParameters = new ArrayList<Parameter>();
983-
984-
// look for constructor-level annotated properties
985-
final Constructor<?> constructor = ReflectionUtils.findConstructor(cls);
986-
if (constructor != null) {
987-
final Type[] genericParameterTypes = constructor.getGenericParameterTypes();
988-
final Annotation[][] annotations = constructor.getParameterAnnotations();
989-
for (int i = 0; i < genericParameterTypes.length; i++) {
990-
globalParameters.addAll(getParameters(genericParameterTypes[i], Arrays.asList(annotations[i])));
991-
}
992-
}
993-
994-
// look for field-level annotated properties
995-
for (Field field : cls.getDeclaredFields()) {
996-
final List<Annotation> annotations = Arrays.asList(field.getAnnotations());
997-
final Collection<Class<? extends Annotation>> types = Collections2.transform(annotations, ReflectionUtils.createAnnotationTypeGetter());
998-
if (!Collections.disjoint(types, FIELD_ANNOTATIONS)) {
999-
globalParameters.addAll(getParameters(field.getGenericType(), annotations));
1000-
}
1001-
}
1002-
1003-
return globalParameters;
1004-
} private static boolean isResourceClass(Class<?> cls) {
980+
private static boolean isResourceClass(Class<?> cls) {
1005981
return cls.getAnnotation(Api.class) != null;
1006982
}
1007983

@@ -1063,14 +1039,4 @@ public Property wrap(String container, Property property) {
10631039

10641040
protected abstract Property doWrap(Property property);
10651041
}
1066-
1067-
static {
1068-
final Set<Class<? extends Annotation>> fieldAnnotations = new HashSet<Class<? extends Annotation>>();
1069-
fieldAnnotations.add(PathParam.class);
1070-
fieldAnnotations.add(QueryParam.class);
1071-
fieldAnnotations.add(HeaderParam.class);
1072-
fieldAnnotations.add(ApiParam.class);
1073-
fieldAnnotations.add(ApiImplicitParam.class);
1074-
FIELD_ANNOTATIONS = Collections.unmodifiableSet(fieldAnnotations);
1075-
}
10761042
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package io.swagger.jaxrs.utils;
2+
3+
import io.swagger.jaxrs.ParameterProcessor;
4+
import io.swagger.jaxrs.ext.SwaggerExtension;
5+
import io.swagger.jaxrs.ext.SwaggerExtensions;
6+
import io.swagger.models.Swagger;
7+
import io.swagger.models.parameters.Parameter;
8+
9+
import java.lang.annotation.Annotation;
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.Field;
12+
import java.lang.reflect.Modifier;
13+
import java.lang.reflect.Type;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.Collections;
17+
import java.util.HashSet;
18+
import java.util.Iterator;
19+
import java.util.List;
20+
21+
public class ReaderUtils {
22+
23+
/**
24+
* Collects constructor-level parameters from class.
25+
*
26+
* @param cls is a class for collecting
27+
* @param swagger is the instance of the Swagger
28+
* @return the collection of supported parameters
29+
*/
30+
public static List<Parameter> collectConstructorParameters(Class<?> cls, Swagger swagger) {
31+
if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) {
32+
return Collections.emptyList();
33+
}
34+
35+
List<Parameter> selected = Collections.emptyList();
36+
int maxParamsCount = 0;
37+
38+
for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
39+
if (!ReflectionUtils.isConstructorCompatible(constructor)
40+
&& !ReflectionUtils.isInject(Arrays.asList(constructor.getDeclaredAnnotations()))) {
41+
continue;
42+
}
43+
44+
final Type[] genericParameterTypes = constructor.getGenericParameterTypes();
45+
final Annotation[][] annotations = constructor.getParameterAnnotations();
46+
47+
int paramsCount = 0;
48+
final List<Parameter> parameters = new ArrayList<Parameter>();
49+
for (int i = 0; i < genericParameterTypes.length; i++) {
50+
final List<Annotation> tmpAnnotations = Arrays.asList(annotations[i]);
51+
if (ReflectionUtils.isContext(tmpAnnotations)) {
52+
paramsCount++;
53+
} else {
54+
final Type genericParameterType = genericParameterTypes[i];
55+
final List<Parameter> tmpParameters = collectParameters(genericParameterType, tmpAnnotations);
56+
if (tmpParameters.size() >= 1) {
57+
for (Parameter tmpParameter : tmpParameters) {
58+
if (ParameterProcessor.applyAnnotations(swagger, tmpParameter, genericParameterType, tmpAnnotations) != null) {
59+
parameters.add(tmpParameter);
60+
}
61+
}
62+
paramsCount++;
63+
}
64+
}
65+
}
66+
67+
if (paramsCount >= maxParamsCount) {
68+
maxParamsCount = paramsCount;
69+
selected = parameters;
70+
}
71+
}
72+
73+
return selected;
74+
}
75+
76+
/**
77+
* Collects field-level parameters from class.
78+
*
79+
* @param cls is a class for collecting
80+
* @param swagger is the instance of the Swagger
81+
* @return the collection of supported parameters
82+
*/
83+
public static List<Parameter> collectFieldParameters(Class<?> cls, Swagger swagger) {
84+
final List<Parameter> parameters = new ArrayList<Parameter>();
85+
for (Field field : cls.getDeclaredFields()) {
86+
final List<Annotation> annotations = Arrays.asList(field.getAnnotations());
87+
final Type genericType = field.getGenericType();
88+
for (Parameter parameter : collectParameters(genericType, annotations)) {
89+
if (ParameterProcessor.applyAnnotations(swagger, parameter, genericType, annotations) != null) {
90+
parameters.add(parameter);
91+
}
92+
}
93+
}
94+
return parameters;
95+
}
96+
97+
private static List<Parameter> collectParameters(Type type, List<Annotation> annotations) {
98+
final Iterator<SwaggerExtension> chain = SwaggerExtensions.chain();
99+
return chain.hasNext() ? chain.next().extractParameters(annotations, type, new HashSet<Type>(), chain) :
100+
Collections.<Parameter>emptyList();
101+
}
102+
}

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

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

3-
import io.swagger.jaxrs.Reader;
4-
53
import io.swagger.converter.PrimitiveType;
64

75
import com.google.common.base.Function;
8-
import com.google.common.collect.Collections2;
9-
106
import org.slf4j.Logger;
117
import org.slf4j.LoggerFactory;
128

13-
import javax.ws.rs.HeaderParam;
14-
import javax.ws.rs.PathParam;
15-
import javax.ws.rs.QueryParam;
16-
179
import java.lang.annotation.Annotation;
1810
import java.lang.reflect.Constructor;
1911
import java.lang.reflect.Method;
2012
import java.lang.reflect.Modifier;
2113
import java.lang.reflect.Type;
2214
import java.util.Arrays;
23-
import java.util.Collection;
24-
import java.util.Collections;
25-
import java.util.HashSet;
26-
import java.util.Set;
15+
import java.util.List;
16+
17+
import javax.ws.rs.core.Context;
2718

2819
public class ReflectionUtils {
29-
private static final Set<Class<? extends Annotation>> CONSTRUCTOR_ANNOTATIONS;
3020
private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtils.class);
3121

3222
public static Type typeFromString(String type) {
@@ -127,38 +117,6 @@ private static boolean hasIdenticalParameters(Class<?>[] srcParameterTypes, Clas
127117
return true;
128118
}
129119

130-
/**
131-
* Searches for constructor suitable for resource instantiation.
132-
* <p>
133-
* If more constructors exists the one with the most injectable parameters will be selected.
134-
*
135-
* @param cls is the class where to search
136-
* @return the suitable constructor
137-
*/
138-
public static Constructor<?> findConstructor(Class<?> cls) {
139-
if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) {
140-
return null;
141-
}
142-
143-
Constructor<?> selected = null;
144-
int selectedCount = 0;
145-
int maxParams = -1;
146-
for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
147-
final Class<?>[] parameterTypes = constructor.getParameterTypes();
148-
if (parameterTypes.length >= maxParams && isCompatible(constructor)) {
149-
if (parameterTypes.length > maxParams) {
150-
maxParams = parameterTypes.length;
151-
selectedCount = 0;
152-
}
153-
154-
selected = constructor;
155-
selectedCount++;
156-
}
157-
}
158-
159-
return selectedCount == 1 ? selected : null;
160-
}
161-
162120
/**
163121
* Returns an implementation of {@link Function} for getting annotation types.
164122
*
@@ -173,43 +131,31 @@ public Class<? extends Annotation> apply(Annotation annotation) {
173131
};
174132
}
175133

176-
/**
177-
* Checks if the passed constructor is suitable for resource instantiation.
178-
* Repeats the logic of the {@link org.glassfish.jersey.internal.inject.JerseyClassAnalyzer#isCompatible(java.lang.reflect.Constructor)}
179-
*
180-
* @param constructor the constructor to be checked
181-
* @return true if the constructor is suitable or false otherwise
182-
*/
183-
private static boolean isCompatible(Constructor<?> constructor) {
184-
for (Annotation annotation : constructor.getAnnotations()) {
134+
public static boolean isContext(List<Annotation> annotations) {
135+
for (Annotation annotation : annotations) {
136+
if (annotation instanceof Context) {
137+
return true;
138+
}
139+
}
140+
return false;
141+
}
142+
143+
public static boolean isInject(List<Annotation> annotations) {
144+
for (Annotation annotation : annotations) {
185145
// use string name to avoid additional dependencies
186146
if ("javax.inject.Inject".equals(annotation.annotationType().getName())) {
187147
return true;
188148
}
189149
}
150+
return false;
151+
}
190152

153+
public static boolean isConstructorCompatible(Constructor<?> constructor) {
191154
if (!Modifier.isPublic(constructor.getModifiers())) {
192155
final int access = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
193156
return constructor.getParameterTypes().length == 0 &&
194157
(constructor.getDeclaringClass().getModifiers() & access) == constructor.getModifiers();
195158
}
196-
197-
for (Annotation[] paramAnnotations : constructor.getParameterAnnotations()) {
198-
final Collection<Class<? extends Annotation>> tmp = Collections2.transform(Arrays.asList(paramAnnotations),
199-
ReflectionUtils.createAnnotationTypeGetter());
200-
if (Collections.disjoint(tmp, CONSTRUCTOR_ANNOTATIONS)) {
201-
return false;
202-
}
203-
}
204-
205159
return true;
206160
}
207-
208-
static {
209-
final Set<Class<? extends Annotation>> constructorAnnotations = new HashSet<Class<? extends Annotation>>();
210-
constructorAnnotations.add(PathParam.class);
211-
constructorAnnotations.add(QueryParam.class);
212-
constructorAnnotations.add(HeaderParam.class);
213-
CONSTRUCTOR_ANNOTATIONS = Collections.unmodifiableSet(constructorAnnotations);
214-
}
215161
}

0 commit comments

Comments
 (0)