001package io.avaje.http.generator.core;
002
003import io.avaje.http.api.Delete;
004import io.avaje.http.api.Form;
005import io.avaje.http.api.Get;
006import io.avaje.http.api.Patch;
007import io.avaje.http.api.Post;
008import io.avaje.http.api.Produces;
009import io.avaje.http.api.Put;
010import io.avaje.http.generator.core.javadoc.Javadoc;
011import io.avaje.http.generator.core.openapi.MethodDocBuilder;
012import io.swagger.v3.oas.annotations.tags.Tag;
013import io.swagger.v3.oas.annotations.tags.Tags;
014
015import javax.lang.model.element.Element;
016import javax.lang.model.element.ExecutableElement;
017import javax.lang.model.element.VariableElement;
018import javax.lang.model.type.ExecutableType;
019import javax.lang.model.type.TypeKind;
020import javax.lang.model.type.TypeMirror;
021import java.lang.annotation.Annotation;
022import java.util.ArrayList;
023import java.util.List;
024
025public class MethodReader {
026
027  private final ProcessingContext ctx;
028  private final ControllerReader bean;
029  private final ExecutableElement element;
030
031  private final boolean isVoid;
032  private final List<MethodParam> params = new ArrayList<>();
033
034  private final Javadoc javadoc;
035
036  private WebMethod webMethod;
037  private String webMethodPath;
038
039  private boolean formMarker;
040
041  /**
042   * Holds enum Roles that are required for the method.
043   */
044  private final List<String> methodRoles;
045
046  private final String produces;
047
048  private final ExecutableType actualExecutable;
049  private final List<? extends TypeMirror> actualParams;
050
051  private final PathSegments pathSegments;
052
053  MethodReader(ControllerReader bean, ExecutableElement element, ExecutableType actualExecutable, ProcessingContext ctx) {
054    this.ctx = ctx;
055    this.bean = bean;
056    this.element = element;
057    this.actualExecutable = actualExecutable;
058    this.actualParams = (actualExecutable == null) ? null : actualExecutable.getParameterTypes();
059    this.isVoid = element.getReturnType().getKind() == TypeKind.VOID;
060    this.methodRoles = Util.findRoles(element);
061    this.javadoc = Javadoc.parse(ctx.getDocComment(element));
062    this.produces = produces(bean);
063
064    initWebMethodViaAnnotation();
065    if (isWebMethod()) {
066      this.pathSegments = PathSegments.parse(Util.combinePath(bean.getPath(), webMethodPath));
067    } else {
068      this.pathSegments = null;
069    }
070  }
071
072  private void initWebMethodViaAnnotation() {
073    Form form = findAnnotation(Form.class);
074    if (form != null) {
075      this.formMarker = true;
076    }
077    Get get = findAnnotation(Get.class);
078    if (get != null) {
079      initSetWebMethod(WebMethod.GET, get.value());
080      return;
081    }
082    Put put = findAnnotation(Put.class);
083    if (put != null) {
084      initSetWebMethod(WebMethod.PUT, put.value());
085      return;
086    }
087    Post post = findAnnotation(Post.class);
088    if (post != null) {
089      initSetWebMethod(WebMethod.POST, post.value());
090      return;
091    }
092    Patch patch = findAnnotation(Patch.class);
093    if (patch != null) {
094      initSetWebMethod(WebMethod.PATCH, patch.value());
095      return;
096    }
097    Delete delete = findAnnotation(Delete.class);
098    if (delete != null) {
099      initSetWebMethod(WebMethod.DELETE, delete.value());
100    }
101  }
102
103  private void initSetWebMethod(WebMethod webMethod, String value) {
104    this.webMethod = webMethod;
105    this.webMethodPath = value;
106  }
107
108  public Javadoc getJavadoc() {
109    return javadoc;
110  }
111
112  private String produces(ControllerReader bean) {
113    final Produces produces = findAnnotation(Produces.class);
114    return (produces != null) ? produces.value() : bean.getProduces();
115  }
116
117  public <A extends Annotation> A findAnnotation(Class<A> type) {
118    A annotation = element.getAnnotation(type);
119    if (annotation != null) {
120      return annotation;
121    }
122
123    return bean.findMethodAnnotation(type, element);
124  }
125
126  private List<String> addTagsToList(Element element, List<String> list) {
127    if (element == null)
128      return list;
129
130    if (element.getAnnotation(Tag.class) != null) {
131      list.add(element.getAnnotation(Tag.class).name());
132    }
133    if (element.getAnnotation(Tags.class) != null) {
134      for (Tag tag : element.getAnnotation(Tags.class).value())
135        list.add(tag.name());
136    }
137    return list;
138  }
139
140  public List<String> getTags() {
141    List<String> tags = new ArrayList<>();
142    tags = addTagsToList(element, tags);
143    return addTagsToList(element.getEnclosingElement(), tags);
144  }
145
146  void read() {
147    if (!methodRoles.isEmpty()) {
148      ctx.platform().methodRoles(methodRoles, bean);
149    }
150
151    // non-path parameters default to form or query parameters based on the
152    // existence of @Form annotation on the method
153    ParamType defaultParamType = (formMarker) ? ParamType.FORMPARAM : ParamType.QUERYPARAM;
154
155    final List<? extends VariableElement> parameters = element.getParameters();
156    for (int i = 0; i < parameters.size(); i++) {
157
158      VariableElement p = parameters.get(i);
159
160      String rawType;
161      if (actualParams != null) {
162        rawType = Util.typeDef(actualParams.get(i));
163      } else {
164        rawType = Util.typeDef(p.asType());
165      }
166
167      MethodParam param = new MethodParam(p, rawType, ctx, defaultParamType, formMarker);
168      params.add(param);
169      param.addImports(bean);
170    }
171  }
172
173  public void buildApiDoc() {
174    buildApiDocumentation(ctx);
175  }
176  /**
177   * Build the OpenAPI documentation for the method / operation.
178   */
179  public void buildApiDocumentation(ProcessingContext ctx) {
180    new MethodDocBuilder(this, ctx.doc()).build();
181  }
182
183  public List<String> roles() {
184    return methodRoles.isEmpty() ? bean.getRoles() : methodRoles;
185  }
186
187  public boolean isWebMethod() {
188    return webMethod != null;
189  }
190
191  public WebMethod getWebMethod() {
192    return webMethod;
193  }
194
195  public List<MethodParam> getParams() {
196    return params;
197  }
198
199  public boolean isVoid() {
200    return isVoid;
201  }
202
203  public String getProduces() {
204    return produces;
205  }
206
207  public TypeMirror getReturnType() {
208    if (actualExecutable != null) {
209      return actualExecutable.getReturnType();
210    }
211    return element.getReturnType();
212  }
213
214  public String getStatusCode() {
215    return Integer.toString(webMethod.statusCode(isVoid));
216  }
217
218  public PathSegments getPathSegments() {
219    return pathSegments;
220  }
221
222  public String getFullPath() {
223    return pathSegments.fullPath();
224  }
225
226  public boolean includeValidate() {
227    return bean.isIncludeValidator() && webMethod != WebMethod.GET;
228  }
229
230  public String simpleName() {
231    return element.getSimpleName().toString();
232  }
233
234  public boolean isFormBody() {
235    for (MethodParam param : params) {
236      if (param.isForm()) {
237        return true;
238      }
239    }
240    return false;
241  }
242
243  public String getBodyType() {
244    for (MethodParam param : params) {
245      if (param.isBody()) {
246        return param.getShortType();
247      }
248    }
249    return null;
250  }
251
252  public String getBodyName() {
253    for (MethodParam param : params) {
254      if (param.isBody()) {
255        return param.getName();
256      }
257    }
258    return "body";
259  }
260
261}