001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017import static org.apache.juneau.rest.util.RestUtils.*; 018 019import java.lang.reflect.Method; 020import java.util.*; 021import java.util.concurrent.*; 022 023import org.apache.juneau.dto.swagger.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.reflect.*; 026import org.apache.juneau.rest.annotation.*; 027import org.apache.juneau.svl.*; 028 029/** 030 * Default implementation of {@link RestInfoProvider}. 031 * 032 * <p> 033 * Subclasses can override these methods to tailor how HTTP REST resources are documented. 034 * 035 * <ul class='seealso'> 036 * <li class='jf'>{@link RestContext#REST_infoProvider} 037 * <li class='link'>{@doc juneau-rest-server.Swagger} 038 * </ul> 039 */ 040public class BasicRestInfoProvider implements RestInfoProvider { 041 042 private final RestContext context; 043 private final String 044 siteName, 045 title, 046 description; 047 private final ConcurrentHashMap<Locale,ConcurrentHashMap<Integer,Swagger>> swaggers = new ConcurrentHashMap<>(); 048 049 /** 050 * Constructor. 051 * 052 * @param context The resource context. 053 */ 054 public BasicRestInfoProvider(RestContext context) { 055 this.context = context; 056 057 Builder b = new Builder(context); 058 this.siteName = b.siteName; 059 this.title = b.title; 060 this.description = b.description; 061 } 062 063 private static final class Builder { 064 String 065 siteName, 066 title, 067 description; 068 069 Builder(RestContext context) { 070 ClassInfo ci = getClassInfo(context.getResource().getClass()); 071 for (RestResource r : ci.getAnnotationsParentFirst(RestResource.class)) { 072 if (! r.siteName().isEmpty()) 073 siteName = r.siteName(); 074 if (r.title().length > 0) 075 title = joinnl(r.title()); 076 if (r.description().length > 0) 077 description = joinnl(r.description()); 078 } 079 } 080 } 081 082 /** 083 * Returns the localized swagger for this REST resource. 084 * 085 * <p> 086 * Subclasses can override this method to customize the Swagger. 087 * 088 * @param req The incoming HTTP request. 089 * @return 090 * A new Swagger instance. 091 * <br>Never <jk>null</jk>. 092 * @throws Exception Error occurred. 093 */ 094 @Override /* RestInfoProvider */ 095 public Swagger getSwagger(RestRequest req) throws Exception { 096 097 Locale locale = req.getLocale(); 098 099 // Find it in the cache. 100 // Swaggers are cached by user locale and an int hash of the @RestMethods they have access to. 101 HashCode userHash = HashCode.create(); 102 for (RestMethodContext sm : context.getCallMethods().values()) 103 if (sm.isRequestAllowed(req)) 104 userHash.add(sm.hashCode()); 105 int hashCode = userHash.get(); 106 107 if (! swaggers.containsKey(locale)) 108 swaggers.putIfAbsent(locale, new ConcurrentHashMap<Integer,Swagger>()); 109 110 Swagger swagger = swaggers.get(locale).get(hashCode); 111 if (swagger != null) 112 return swagger; 113 114 // Wasn't cached...need to create one. 115 swagger = new SwaggerGenerator(req).getSwagger(); 116 117 swaggers.get(locale).put(hashCode, swagger); 118 119 return swagger; 120 } 121 122 /** 123 * Returns the localized summary of the specified java method on this servlet. 124 * 125 * <p> 126 * Subclasses can override this method to provide their own summary. 127 * 128 * <p> 129 * The default implementation returns the value from the following locations (whichever matches first): 130 * <ol class='spaced-list'> 131 * <li>{@link RestMethod#summary() @RestMethod(summary)} annotation. 132 * <h5 class='figure'>Examples:</h5> 133 * <p class='bcode w800'> 134 * <cc>// Direct value</cc> 135 * <ja>@RestMethod</ja>(summary=<js>"Summary of my method"</js>) 136 * <jk>public</jk> Object myMethod() {...} 137 * 138 * <cc>// Pulled from some other location</cc> 139 * <ja>@RestMethod</ja>(summary=<js>"$L{myLocalizedSummary}"</js>) 140 * <jk>public</jk> Object myMethod() {...} 141 * </p> 142 * <li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 143 * on the resource class, then any parent classes. 144 * <ol> 145 * <li><ck>[ClassName].[javaMethodName].summary</ck> 146 * <li><ck>[javaMethodName].summary</ck> 147 * </ol> 148 * <br>Value can contain any SVL variables defined on the {@link RestMethod#summary() @RestMethod(summary)} annotation. 149 * <h5 class='figure'>Examples:</h5> 150 * <p class='bcode w800'> 151 * <cc>// Direct value</cc> 152 * <ck>MyClass.myMethod.summary</ck> = <cv>Summary of my method.</cv> 153 * 154 * <cc>// Pulled from some other location</cc> 155 * <ck>MyClass.myMethod.summary</ck> = <cv>$C{MyStrings/MyClass.myMethod.summary}</cv> 156 * </p> 157 * </ol> 158 * 159 * @param method The Java method annotated with {@link RestMethod @RestMethod}. 160 * @param req The current request. 161 * @return The localized summary of the method, or <jk>null</jk> if none was found. 162 * @throws Exception Error occurred. 163 */ 164 @Override /* RestInfoProvider */ 165 public String getMethodSummary(Method method, RestRequest req) throws Exception { 166 VarResolverSession vr = req.getVarResolverSession(); 167 168 String s = getMethodInfo(method).getAnnotation(RestMethod.class).summary(); 169 if (s.isEmpty()) { 170 Operation o = getSwaggerOperation(method, req); 171 if (o != null) 172 s = o.getSummary(); 173 } 174 175 return isEmpty(s) ? null : vr.resolve(s); 176 } 177 178 /** 179 * Returns the localized description of the specified java method on this servlet. 180 * 181 * <p> 182 * Subclasses can override this method to provide their own description. 183 * 184 * <p> 185 * The default implementation returns the value from the following locations (whichever matches first): 186 * <ol class='spaced-list'> 187 * <li>{@link RestMethod#description() @RestMethod(description)} annotation. 188 * <h5 class='figure'>Examples:</h5> 189 * <p class='bcode w800'> 190 * <cc>// Direct value</cc> 191 * <ja>@RestMethod</ja>(description=<js>"Description of my method"</js>) 192 * <jk>public</jk> Object myMethod() {...} 193 * 194 * <cc>// Pulled from some other location</cc> 195 * <ja>@RestMethod</ja>(description=<js>"$L{myLocalizedDescription}"</js>) 196 * <jk>public</jk> Object myMethod() {...} 197 * </p> 198 * <li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 199 * on the resource class, then any parent classes. 200 * <ol> 201 * <li><ck>[ClassName].[javaMethodName].description</ck> 202 * <li><ck>[javaMethodName].description</ck> 203 * </ol> 204 * <br>Value can contain any SVL variables defined on the {@link RestMethod#description() @RestMethod(description)} annotation. 205 * <h5 class='figure'>Examples:</h5> 206 * <p class='bcode w800'> 207 * <cc>// Direct value</cc> 208 * <ck>MyClass.myMethod.description</ck> = <cv>Description of my method.</cv> 209 * 210 * <cc>// Pulled from some other location</cc> 211 * <ck>MyClass.myMethod.description</ck> = <cv>$C{MyStrings/MyClass.myMethod.description}</cv> 212 * </p> 213 * </ol> 214 * 215 * @param method The Java method annotated with {@link RestMethod @RestMethod}. 216 * @param req The current request. 217 * @return The localized description of the method, or <jk>null</jk> if none was found. 218 * @throws Exception Error occurred. 219 */ 220 @Override /* RestInfoProvider */ 221 public String getMethodDescription(Method method, RestRequest req) throws Exception { 222 VarResolverSession vr = req.getVarResolverSession(); 223 224 String s = joinnl(getMethodInfo(method).getAnnotation(RestMethod.class).description()); 225 if (s.isEmpty()) { 226 Operation o = getSwaggerOperation(method, req); 227 if (o != null) 228 s = o.getDescription(); 229 } 230 231 return isEmpty(s) ? null : vr.resolve(s); 232 } 233 234 /** 235 * Returns the localized site name of this REST resource. 236 * 237 * <p> 238 * Subclasses can override this method to provide their own site name. 239 * 240 * <p> 241 * The default implementation returns the value from the following locations (whichever matches first): 242 * <ol class='spaced-list'> 243 * <li>{@link RestResource#siteName() @RestResource(siteName)} annotation on this class, and then any parent classes. 244 * <h5 class='figure'>Examples:</h5> 245 * <p class='bcode w800'> 246 * <jc>// Direct value</jc> 247 * <ja>@RestResource</ja>(siteName=<js>"My Site"</js>) 248 * <jk>public class</jk> MyResource {...} 249 * 250 * <jc>// Pulled from some other location</jc> 251 * <ja>@RestResource</ja>(siteName=<js>"$L{myLocalizedSiteName}"</js>) 252 * <jk>public class</jk> MyResource {...} 253 * </p> 254 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 255 * on the resource class, then any parent classes. 256 * <ol> 257 * <li><ck>[ClassName].siteName</ck> 258 * <li><ck>siteName</ck> 259 * </ol> 260 * <br>Value can contain any SVL variables defined on the {@link RestResource#siteName() @RestResource(siteName)} annotation. 261 * <h5 class='figure'>Examples:</h5> 262 * <p class='bcode w800'> 263 * <cc>// Direct value</cc> 264 * <ck>MyClass.siteName</ck> = <cv>My Site</cv> 265 * 266 * <cc>// Pulled from some other location</cc> 267 * <ck>MyClass.siteName</ck> = <cv>$C{MyStrings/MyClass.siteName}</cv> 268 * </p> 269 * </ol> 270 * 271 * @param req The current request. 272 * @return The localized site name of this REST resource, or <jk>null</jk> if none was found. 273 * @throws Exception Error occurred. 274 */ 275 @Override /* RestInfoProvider */ 276 public String getSiteName(RestRequest req) throws Exception { 277 VarResolverSession vr = req.getVarResolverSession(); 278 if (siteName != null) 279 return vr.resolve(siteName); 280 String siteName = context.getMessages().findFirstString(req.getLocale(), "siteName"); 281 if (siteName != null) 282 return vr.resolve(siteName); 283 return null; 284 } 285 286 /** 287 * Returns the localized title of this REST resource. 288 * 289 * <p> 290 * Subclasses can override this method to provide their own title. 291 * 292 * <p> 293 * The default implementation returns the value from the following locations (whichever matches first): 294 * <ol class='spaced-list'> 295 * <li>{@link RestResource#title() @RestResource(siteName)} annotation on this class, and then any parent classes. 296 * <h5 class='figure'>Examples:</h5> 297 * <p class='bcode w800'> 298 * <jc>// Direct value</jc> 299 * <ja>@RestResource</ja>(title=<js>"My Resource"</js>) 300 * <jk>public class</jk> MyResource {...} 301 * 302 * <jc>// Pulled from some other location</jc> 303 * <ja>@RestResource</ja>(title=<js>"$L{myLocalizedTitle}"</js>) 304 * <jk>public class</jk> MyResource {...} 305 * </p> 306 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 307 * on the resource class, then any parent classes. 308 * <ol> 309 * <li><ck>[ClassName].title</ck> 310 * <li><ck>title</ck> 311 * </ol> 312 * <br>Value can contain any SVL variables defined on the {@link RestResource#title() @RestResource(title)} annotation. 313 * <h5 class='figure'>Examples:</h5> 314 * <p class='bcode w800'> 315 * <cc>// Direct value</cc> 316 * <ck>MyClass.title</ck> = <cv>My Resource</cv> 317 * 318 * <cc>// Pulled from some other location</cc> 319 * <ck>MyClass.title</ck> = <cv>$C{MyStrings/MyClass.title}</cv> 320 * </p> 321 * <li><ck>/info/title</ck> entry in swagger file. 322 * </ol> 323 * 324 * @param req The current request. 325 * @return The localized title of this REST resource, or <jk>null</jk> if none was found. 326 * @throws Exception Error occurred. 327 */ 328 @Override /* RestInfoProvider */ 329 public String getTitle(RestRequest req) throws Exception { 330 VarResolverSession vr = req.getVarResolverSession(); 331 if (title != null) 332 return vr.resolve(title); 333 String title = context.getMessages().findFirstString(req.getLocale(), "title"); 334 if (title != null) 335 return vr.resolve(title); 336 return null; 337 } 338 339 /** 340 * Returns the localized description of this REST resource. 341 * 342 * <p> 343 * Subclasses can override this method to provide their own description. 344 * 345 * <p> 346 * The default implementation returns the value from the following locations (whichever matches first): 347 * <ol class='spaced-list'> 348 * <li>{@link RestResource#description() @RestResource(description)} annotation on this class, and then any parent classes. 349 * <h5 class='figure'>Examples:</h5> 350 * <p class='bcode w800'> 351 * <jc>// Direct value</jc> 352 * <ja>@RestResource</ja>(description=<js>"My Resource"</js>) 353 * <jk>public class</jk> MyResource {...} 354 * 355 * <jc>// Pulled from some other location</jc> 356 * <ja>@RestResource</ja>(description=<js>"$L{myLocalizedDescription}"</js>) 357 * <jk>public class</jk> MyResource {...} 358 * </p> 359 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 360 * on the resource class, then any parent classes. 361 * <ol> 362 * <li><ck>[ClassName].description</ck> 363 * <li><ck>description</ck> 364 * </ol> 365 * <br>Value can contain any SVL variables defined on the {@link RestResource#description() @RestResource(description)} annotation. 366 * <h5 class='figure'>Examples:</h5> 367 * <p class='bcode w800'> 368 * <cc>// Direct value</cc> 369 * <ck>MyClass.description</ck> = <cv>My Resource</cv> 370 * 371 * <cc>// Pulled from some other location</cc> 372 * <ck>MyClass.description</ck> = <cv>$C{MyStrings/MyClass.description}</cv> 373 * </p> 374 * <li><ck>/info/description</ck> entry in swagger file. 375 * </ol> 376 * 377 * @param req The current request. 378 * @return The localized description of this REST resource, or <jk>null</jk> if none was was found. 379 * @throws Exception Error occurred. 380 */ 381 @Override /* RestInfoProvider */ 382 public String getDescription(RestRequest req) throws Exception { 383 VarResolverSession vr = req.getVarResolverSession(); 384 if (description != null) 385 return vr.resolve(description); 386 String description = context.getMessages().findFirstString(req.getLocale(), "description"); 387 if (description != null) 388 return vr.resolve(description); 389 return null; 390 } 391 392 //----------------------------------------------------------------------------------------------------------------- 393 // Utility methods 394 //----------------------------------------------------------------------------------------------------------------- 395 396 private Operation getSwaggerOperation(Method method, RestRequest req) throws Exception { 397 398 Swagger s = getSwagger(req); 399 if (s != null) { 400 Map<String,OperationMap> sp = s.getPaths(); 401 if (sp != null) { 402 Map<String,Operation> spp = sp.get(fixMethodPath(getMethodInfo(method).getAnnotation(RestMethod.class).path())); 403 if (spp != null) 404 return spp.get(req.getMethod()); 405 } 406 } 407 return null; 408 } 409 410 static String joinnl(String[] ss) { 411 if (ss.length == 0) 412 return ""; 413 return StringUtils.joinnl(ss).trim(); 414 } 415}