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}