001package io.avaje.http.generator.core; 002 003import io.avaje.http.api.Path; 004import io.avaje.http.api.Produces; 005import io.swagger.v3.oas.annotations.Hidden; 006 007import javax.lang.model.element.Element; 008import javax.lang.model.element.ElementKind; 009import javax.lang.model.element.ExecutableElement; 010import javax.lang.model.element.TypeElement; 011import javax.lang.model.type.DeclaredType; 012import javax.lang.model.type.ExecutableType; 013import javax.lang.model.type.TypeKind; 014import javax.lang.model.type.TypeMirror; 015import javax.lang.model.util.ElementFilter; 016import javax.validation.Valid; 017import java.lang.annotation.Annotation; 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Set; 021import java.util.TreeSet; 022 023/** 024 * Reads the type information for the Controller (bean). 025 */ 026public class ControllerReader { 027 028 private final ProcessingContext ctx; 029 030 private final TypeElement beanType; 031 032 private final List<Element> interfaces; 033 034 private final List<ExecutableElement> interfaceMethods; 035 036 private final List<String> roles; 037 038 private final List<MethodReader> methods = new ArrayList<>(); 039 040 private final Set<String> staticImportTypes = new TreeSet<>(); 041 042 private final Set<String> importTypes = new TreeSet<>(); 043 044 /** 045 * The produces media type for the controller. Null implies JSON. 046 */ 047 private final String produces; 048 049 private final boolean includeValidator; 050 051 /** 052 * Flag set when the controller is dependant on a request scope type. 053 */ 054 private boolean requestScope; 055 056 private boolean docHidden; 057 058 ControllerReader(TypeElement beanType, ProcessingContext ctx) { 059 this.beanType = beanType; 060 this.ctx = ctx; 061 this.interfaces = initInterfaces(); 062 this.interfaceMethods = initInterfaceMethods(); 063 this.roles = Util.findRoles(beanType); 064 importTypes.add(Constants.GENERATED); 065 if (ctx.isOpenApiAvailable()) { 066 docHidden = initDocHidden(); 067 } 068 includeValidator = initIncludeValidator(); 069 importTypes.add(Constants.SINGLETON); 070 importTypes.add(Constants.IMPORT_CONTROLLER); 071 importTypes.add(beanType.getQualifiedName().toString()); 072 if (includeValidator) { 073 importTypes.add(Constants.VALIDATOR); 074 } 075 this.produces = initProduces(); 076 } 077 078 private List<Element> initInterfaces() { 079 List<Element> interfaces = new ArrayList<>(); 080 for (TypeMirror anInterface : beanType.getInterfaces()) { 081 final Element ifaceElement = ctx.asElement(anInterface); 082 if (ifaceElement.getAnnotation(Path.class) != null) { 083 interfaces.add(ifaceElement); 084 } 085 } 086 return interfaces; 087 } 088 089 private List<ExecutableElement> initInterfaceMethods() { 090 List<ExecutableElement> ifaceMethods = new ArrayList<>(); 091 for (Element anInterface : interfaces) { 092 ifaceMethods.addAll(ElementFilter.methodsIn(anInterface.getEnclosedElements())); 093 } 094 return ifaceMethods; 095 } 096 097 private <A extends Annotation> A findAnnotation(Class<A> type) { 098 A annotation = beanType.getAnnotation(type); 099 if (annotation != null) { 100 return annotation; 101 } 102 for (Element anInterface : interfaces) { 103 annotation = anInterface.getAnnotation(type); 104 if (annotation != null) { 105 return annotation; 106 } 107 } 108 return null; 109 } 110 111 <A extends Annotation> A findMethodAnnotation(Class<A> type, ExecutableElement element) { 112 for (ExecutableElement interfaceMethod : interfaceMethods) { 113 if (matchMethod(interfaceMethod, element)) { 114 final A annotation = interfaceMethod.getAnnotation(type); 115 if (annotation != null) { 116 return annotation; 117 } 118 } 119 } 120 return null; 121 } 122 123 private boolean matchMethod(ExecutableElement interfaceMethod, ExecutableElement element) { 124 return interfaceMethod.toString().equals(element.toString()); 125 } 126 127 private String initProduces() { 128 final Produces produces = findAnnotation(Produces.class); 129 return (produces == null) ? null : produces.value(); 130 } 131 132 private boolean initDocHidden() { 133 return findAnnotation(Hidden.class) != null; 134 } 135 136 private boolean initIncludeValidator() { 137 return findAnnotation(Valid.class) != null; 138 } 139 140 String getProduces() { 141 return produces; 142 } 143 144 TypeElement getBeanType() { 145 return beanType; 146 } 147 148 public boolean isDocHidden() { 149 return docHidden; 150 } 151 152 public boolean isIncludeValidator() { 153 return includeValidator; 154 } 155 156 /** 157 * Return true if the controller has request scoped dependencies. 158 * In that case a BeanFactory will have been generated. 159 */ 160 boolean isRequestScoped() { 161 return requestScope; 162 } 163 164 void read() { 165 if (!roles.isEmpty()) { 166 ctx.platform().controllerRoles(roles, this); 167 } 168 for (Element element : beanType.getEnclosedElements()) { 169 if (element.getKind() == ElementKind.METHOD) { 170 readMethod((ExecutableElement) element); 171 } else if (element.getKind() == ElementKind.FIELD) { 172 readField(element); 173 } 174 } 175 readSuper(beanType); 176 } 177 178 private void readField(Element element) { 179 if (!requestScope) { 180 final String rawType = element.asType().toString(); 181 requestScope = RequestScopeTypes.isRequestType(rawType); 182 } 183 } 184 185 /** 186 * Read methods from superclasses taking into account generics. 187 */ 188 private void readSuper(TypeElement beanType) { 189 TypeMirror superclass = beanType.getSuperclass(); 190 if (superclass.getKind() != TypeKind.NONE) { 191 DeclaredType declaredType = (DeclaredType) superclass; 192 final Element superElement = ctx.asElement(superclass); 193 if (!"java.lang.Object".equals(superElement.toString())) { 194 for (Element element : superElement.getEnclosedElements()) { 195 if (element.getKind() == ElementKind.METHOD) { 196 readMethod((ExecutableElement) element, declaredType); 197 } else if (element.getKind() == ElementKind.FIELD) { 198 readField(element); 199 } 200 } 201 if (superElement instanceof TypeElement) { 202 readSuper((TypeElement) superElement); 203 } 204 } 205 } 206 } 207 208 private void readMethod(ExecutableElement element) { 209 readMethod(element, null); 210 } 211 212 private void readMethod(ExecutableElement method, DeclaredType declaredType) { 213 214 ExecutableType actualExecutable = null; 215 if (declaredType != null) { 216 // actual taking into account generics 217 actualExecutable = (ExecutableType) ctx.asMemberOf(declaredType, method); 218 } 219 220 MethodReader methodReader = new MethodReader(this, method, actualExecutable, ctx); 221 if (methodReader.isWebMethod()) { 222 methodReader.read(); 223 methods.add(methodReader); 224 } 225 } 226 227 public List<String> getRoles() { 228 return roles; 229 } 230 231 public List<MethodReader> getMethods() { 232 return methods; 233 } 234 235 public String getPath() { 236 Path path = findAnnotation(Path.class); 237 if (path == null) { 238 return null; 239 } 240 return Util.trimPath(path.value()); 241 } 242 243 public void addImportType(String rawType) { 244 importTypes.add(rawType); 245 } 246 247 public void addStaticImportType(String rawType) { 248 staticImportTypes.add(rawType); 249 } 250 251 public Set<String> getStaticImportTypes() { 252 return staticImportTypes; 253 } 254 255 public Set<String> getImportTypes() { 256 return importTypes; 257 } 258 259}