1 package com.atlassian.plugins.rest.doclet.generators.resourcedoc;
2
3 import com.atlassian.annotations.ExperimentalApi;
4 import com.atlassian.plugins.rest.doclet.generators.schema.RichClass;
5 import com.atlassian.rest.annotation.RequestType;
6 import com.atlassian.rest.annotation.ResponseType;
7 import com.atlassian.rest.annotation.ResponseTypes;
8 import com.google.common.collect.ImmutableList;
9 import com.google.common.collect.ImmutableSet;
10 import com.google.common.collect.Lists;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import javax.ws.rs.core.Response;
15 import java.lang.reflect.AnnotatedElement;
16 import java.lang.reflect.Method;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Optional;
22
23 import static com.atlassian.rest.annotation.ResponseType.StatusType.SUCCESS;
24 import static java.util.Optional.empty;
25
26 public class RestMethod {
27 private static final Logger log = LoggerFactory.getLogger(RestMethod.class);
28
29 private final Class<?> resourceClass;
30 private final Method method;
31
32 private RestMethod(final Class<?> resourceClass, final Method method) {
33 this.resourceClass = resourceClass;
34 this.method = method;
35 }
36
37 public boolean isExperimental() {
38 return method.getAnnotation(ExperimentalApi.class) != null;
39 }
40
41 public boolean isDeprecated() {
42 return method.getAnnotation(Deprecated.class) != null;
43 }
44
45 public static RestMethod restMethod(Class<?> resourceClass, final Method method) {
46 return new RestMethod(resourceClass, method);
47 }
48
49 public Optional<RichClass> getRequestType() {
50 Optional<RichClass> typeFromAnnotation = empty();
51 Optional<RichClass> typeFromParameter = empty();
52
53 if (method.isAnnotationPresent(RequestType.class)) {
54 RequestType requestType = method.getAnnotation(RequestType.class);
55 typeFromAnnotation = Optional.of(RichClass.of(requestType.value(), requestType.genericTypes()));
56 }
57
58 for (int i = 0; i < method.getParameterTypes().length; i++) {
59 if (method.getParameterAnnotations()[i].length == 0) {
60 typeFromParameter = Optional.of(RichClass.of(method.getGenericParameterTypes()[i]));
61 break;
62 }
63 }
64
65 if (typeFromAnnotation.isPresent() && !typeFromAnnotation.equals(typeFromParameter)) {
66 log.warn(String.format("Method %s.%s declares request type that is different than the actual request "
67 + "parameter of this method. This may result in inaccurate documentation.", resourceClass.getSimpleName(), method.getName()));
68 }
69
70 return typeFromAnnotation.isPresent() ? typeFromAnnotation : typeFromParameter;
71 }
72
73 public List<RichClass> responseTypesFor(int status) {
74 List<RichClass> types = Lists.newArrayList();
75
76 for (ResponseType responseType : declaredResponseTypes()) {
77 if (status == responseType.status() || matchesStatusType(status, responseType)) {
78 if (!isVoidType(responseType)) {
79 types.add(RichClass.of(responseType.value(), responseType.genericTypes()));
80 }
81 }
82 }
83
84 if (SUCCESS.matches(status) &&
85 !method.getReturnType().equals(Response.class) &&
86 !"void".equalsIgnoreCase(method.getGenericReturnType().getTypeName())) {
87 RichClass actualReturnType = RichClass.of(method.getGenericReturnType());
88
89 if (!types.isEmpty() && !Collections.singletonList(actualReturnType).equals(types)) {
90 log.warn(String.format("Method %s.%s declares response type for success response that is different than the actual return "
91 + "type of this method. This may result in inaccurate documentation.", resourceClass.getSimpleName(), method.getName()));
92 } else {
93 return Collections.singletonList(actualReturnType);
94 }
95 }
96
97 return ImmutableList.copyOf(types);
98 }
99
100 private boolean matchesStatusType(int status, ResponseType responseType) {
101 return responseType.status() == 0
102 && responseType.statusType().matches(status);
103 }
104
105 private boolean isVoidType(ResponseType responseType) {
106 return ImmutableSet.of(Void.class, void.class).contains(responseType.value());
107 }
108
109 private Iterable<ResponseType> declaredResponseTypes() {
110 List<ResponseType> responseTypes = Lists.newArrayList();
111
112 responseTypes.addAll(responseTypes(method));
113 responseTypes.addAll(responseTypes(resourceClass));
114
115 return responseTypes;
116 }
117
118 private Collection<ResponseType> responseTypes(final AnnotatedElement element) {
119 if (element.isAnnotationPresent(ResponseType.class)) {
120 return Collections.singleton(element.getAnnotation(ResponseType.class));
121 }
122
123 if (element.isAnnotationPresent(ResponseTypes.class)) {
124 return Arrays.asList(element.getAnnotation(ResponseTypes.class).value());
125 }
126
127 return Collections.emptyList();
128 }
129 }