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.*; 017 018import java.lang.reflect.Method; 019import java.util.*; 020import java.util.concurrent.*; 021 022import org.apache.juneau.dto.swagger.*; 023import org.apache.juneau.http.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.rest.annotation.*; 026import org.apache.juneau.svl.*; 027 028/** 029 * Default implementation of {@link RestInfoProvider}. 030 * 031 * <p> 032 * Subclasses can override these methods to tailor how HTTP REST resources are documented. 033 * 034 * <h5 class='section'>See Also:</h5> 035 * <ul> 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 for (RestResource r : getAnnotationsParentFirst(RestResource.class, context.getResource().getClass())) { 071 if (! r.siteName().isEmpty()) 072 siteName = r.siteName(); 073 if (r.title().length > 0) 074 title = joinnl(r.title()); 075 if (r.description().length > 0) 076 description = joinnl(r.description()); 077 } 078 } 079 } 080 081 /** 082 * Returns the localized swagger for this REST resource. 083 * 084 * <p> 085 * Subclasses can override this method to customize the Swagger. 086 * 087 * @param req The incoming HTTP request. 088 * @return 089 * A new Swagger instance. 090 * <br>Never <jk>null</jk>. 091 * @throws Exception 092 */ 093 @Override /* RestInfoProvider */ 094 public Swagger getSwagger(RestRequest req) throws Exception { 095 096 Locale locale = req.getLocale(); 097 098 // Find it in the cache. 099 // Swaggers are cached by user locale and an int hash of the @RestMethods they have access to. 100 HashCode userHash = HashCode.create(); 101 for (RestJavaMethod sm : context.getCallMethods().values()) 102 if (sm.isRequestAllowed(req)) 103 userHash.add(sm.hashCode()); 104 int hashCode = userHash.get(); 105 106 if (! swaggers.containsKey(locale)) 107 swaggers.putIfAbsent(locale, new ConcurrentHashMap<Integer,Swagger>()); 108 109 Swagger swagger = swaggers.get(locale).get(hashCode); 110 if (swagger != null) 111 return swagger; 112 113 // Wasn't cached...need to create one. 114 swagger = new SwaggerGenerator(req).getSwagger(); 115 116 swaggers.get(locale).put(hashCode, swagger); 117 118 return swagger; 119 } 120 121 /** 122 * Returns the localized summary of the specified java method on this servlet. 123 * 124 * <p> 125 * Subclasses can override this method to provide their own summary. 126 * 127 * <p> 128 * The default implementation returns the value from the following locations (whichever matches first): 129 * <ol class='spaced-list'> 130 * <li>{@link RestMethod#summary() @RestMethod(summary)} annotation. 131 * <h5 class='figure'>Examples:</h5> 132 * <p class='bcode w800'> 133 * <cc>// Direct value</cc> 134 * <ja>@RestMethod</ja>(summary=<js>"Summary of my method"</js>) 135 * <jk>public</jk> Object myMethod() {...} 136 * 137 * <cc>// Pulled from some other location</cc> 138 * <ja>@RestMethod</ja>(summary=<js>"$L{myLocalizedSummary}"</js>) 139 * <jk>public</jk> Object myMethod() {...} 140 * </p> 141 * <li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 142 * on the resource class, then any parent classes. 143 * <ol> 144 * <li><ck>[ClassName].[javaMethodName].summary</ck> 145 * <li><ck>[javaMethodName].summary</ck> 146 * </ol> 147 * <br>Value can contain any SVL variables defined on the {@link RestMethod#summary() @RestMethod(summary)} annotation. 148 * <h5 class='figure'>Examples:</h5> 149 * <p class='bcode w800'> 150 * <cc>// Direct value</cc> 151 * <ck>MyClass.myMethod.summary</ck> = <cv>Summary of my method.</cv> 152 * 153 * <cc>// Pulled from some other location</cc> 154 * <ck>MyClass.myMethod.summary</ck> = <cv>$C{MyStrings/MyClass.myMethod.summary}</cv> 155 * </p> 156 * </ol> 157 * 158 * @param method The Java method annotated with {@link RestMethod @RestMethod}. 159 * @param req The current request. 160 * @return The localized summary of the method, or <jk>null</jk> if none was found. 161 * @throws Exception 162 */ 163 @Override /* RestInfoProvider */ 164 public String getMethodSummary(Method method, RestRequest req) throws Exception { 165 VarResolverSession vr = req.getVarResolverSession(); 166 167 String s = getAnnotation(RestMethod.class, method).summary(); 168 if (s.isEmpty()) { 169 Operation o = getSwaggerOperation(method, req); 170 if (o != null) 171 s = o.getSummary(); 172 } 173 174 return isEmpty(s) ? null : vr.resolve(s); 175 } 176 177 /** 178 * Returns the localized description of the specified java method on this servlet. 179 * 180 * <p> 181 * Subclasses can override this method to provide their own description. 182 * 183 * <p> 184 * The default implementation returns the value from the following locations (whichever matches first): 185 * <ol class='spaced-list'> 186 * <li>{@link RestMethod#description() @RestMethod(description)} annotation. 187 * <h5 class='figure'>Examples:</h5> 188 * <p class='bcode w800'> 189 * <cc>// Direct value</cc> 190 * <ja>@RestMethod</ja>(description=<js>"Description of my method"</js>) 191 * <jk>public</jk> Object myMethod() {...} 192 * 193 * <cc>// Pulled from some other location</cc> 194 * <ja>@RestMethod</ja>(description=<js>"$L{myLocalizedDescription}"</js>) 195 * <jk>public</jk> Object myMethod() {...} 196 * </p> 197 * <li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 198 * on the resource class, then any parent classes. 199 * <ol> 200 * <li><ck>[ClassName].[javaMethodName].description</ck> 201 * <li><ck>[javaMethodName].description</ck> 202 * </ol> 203 * <br>Value can contain any SVL variables defined on the {@link RestMethod#description() @RestMethod(description)} annotation. 204 * <h5 class='figure'>Examples:</h5> 205 * <p class='bcode w800'> 206 * <cc>// Direct value</cc> 207 * <ck>MyClass.myMethod.description</ck> = <cv>Description of my method.</cv> 208 * 209 * <cc>// Pulled from some other location</cc> 210 * <ck>MyClass.myMethod.description</ck> = <cv>$C{MyStrings/MyClass.myMethod.description}</cv> 211 * </p> 212 * </ol> 213 * 214 * @param method The Java method annotated with {@link RestMethod @RestMethod}. 215 * @param req The current request. 216 * @return The localized description of the method, or <jk>null</jk> if none was found. 217 * @throws Exception 218 */ 219 @Override /* RestInfoProvider */ 220 public String getMethodDescription(Method method, RestRequest req) throws Exception { 221 VarResolverSession vr = req.getVarResolverSession(); 222 223 String s = joinnl(getAnnotation(RestMethod.class, method).description()); 224 if (s.isEmpty()) { 225 Operation o = getSwaggerOperation(method, req); 226 if (o != null) 227 s = o.getDescription(); 228 } 229 230 return isEmpty(s) ? null : vr.resolve(s); 231 } 232 233 /** 234 * Returns the localized site name of this REST resource. 235 * 236 * <p> 237 * Subclasses can override this method to provide their own site name. 238 * 239 * <p> 240 * The default implementation returns the value from the following locations (whichever matches first): 241 * <ol class='spaced-list'> 242 * <li>{@link RestResource#siteName() @RestResource(siteName)} annotation on this class, and then any parent classes. 243 * <h5 class='figure'>Examples:</h5> 244 * <p class='bcode w800'> 245 * <jc>// Direct value</jc> 246 * <ja>@RestResource</ja>(siteName=<js>"My Site"</js>) 247 * <jk>public class</jk> MyResource {...} 248 * 249 * <jc>// Pulled from some other location</jc> 250 * <ja>@RestResource</ja>(siteName=<js>"$L{myLocalizedSiteName}"</js>) 251 * <jk>public class</jk> MyResource {...} 252 * </p> 253 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 254 * on the resource class, then any parent classes. 255 * <ol> 256 * <li><ck>[ClassName].siteName</ck> 257 * <li><ck>siteName</ck> 258 * </ol> 259 * <br>Value can contain any SVL variables defined on the {@link RestResource#siteName() @RestResource(siteName)} annotation. 260 * <h5 class='figure'>Examples:</h5> 261 * <p class='bcode w800'> 262 * <cc>// Direct value</cc> 263 * <ck>MyClass.siteName</ck> = <cv>My Site</cv> 264 * 265 * <cc>// Pulled from some other location</cc> 266 * <ck>MyClass.siteName</ck> = <cv>$C{MyStrings/MyClass.siteName}</cv> 267 * </p> 268 * </ol> 269 * 270 * @param req The current request. 271 * @return The localized site name of this REST resource, or <jk>null</jk> if none was found. 272 * @throws Exception 273 */ 274 @Override /* RestInfoProvider */ 275 public String getSiteName(RestRequest req) throws Exception { 276 VarResolverSession vr = req.getVarResolverSession(); 277 if (siteName != null) 278 return vr.resolve(siteName); 279 String siteName = context.getMessages().findFirstString(req.getLocale(), "siteName"); 280 if (siteName != null) 281 return vr.resolve(siteName); 282 return null; 283 } 284 285 /** 286 * Returns the localized title of this REST resource. 287 * 288 * <p> 289 * Subclasses can override this method to provide their own title. 290 * 291 * <p> 292 * The default implementation returns the value from the following locations (whichever matches first): 293 * <ol class='spaced-list'> 294 * <li>{@link RestResource#title() @RestResource(siteName)} annotation on this class, and then any parent classes. 295 * <h5 class='figure'>Examples:</h5> 296 * <p class='bcode w800'> 297 * <jc>// Direct value</jc> 298 * <ja>@RestResource</ja>(title=<js>"My Resource"</js>) 299 * <jk>public class</jk> MyResource {...} 300 * 301 * <jc>// Pulled from some other location</jc> 302 * <ja>@RestResource</ja>(title=<js>"$L{myLocalizedTitle}"</js>) 303 * <jk>public class</jk> MyResource {...} 304 * </p> 305 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 306 * on the resource class, then any parent classes. 307 * <ol> 308 * <li><ck>[ClassName].title</ck> 309 * <li><ck>title</ck> 310 * </ol> 311 * <br>Value can contain any SVL variables defined on the {@link RestResource#title() @RestResource(title)} annotation. 312 * <h5 class='figure'>Examples:</h5> 313 * <p class='bcode w800'> 314 * <cc>// Direct value</cc> 315 * <ck>MyClass.title</ck> = <cv>My Resource</cv> 316 * 317 * <cc>// Pulled from some other location</cc> 318 * <ck>MyClass.title</ck> = <cv>$C{MyStrings/MyClass.title}</cv> 319 * </p> 320 * <li><ck>/info/title</ck> entry in swagger file. 321 * </ol> 322 * 323 * @param req The current request. 324 * @return The localized title of this REST resource, or <jk>null</jk> if none was found. 325 * @throws Exception 326 */ 327 @Override /* RestInfoProvider */ 328 public String getTitle(RestRequest req) throws Exception { 329 VarResolverSession vr = req.getVarResolverSession(); 330 if (title != null) 331 return vr.resolve(title); 332 String title = context.getMessages().findFirstString(req.getLocale(), "title"); 333 if (title != null) 334 return vr.resolve(title); 335 return null; 336 } 337 338 /** 339 * Returns the localized description of this REST resource. 340 * 341 * <p> 342 * Subclasses can override this method to provide their own description. 343 * 344 * <p> 345 * The default implementation returns the value from the following locations (whichever matches first): 346 * <ol class='spaced-list'> 347 * <li>{@link RestResource#description() @RestResource(description)} annotation on this class, and then any parent classes. 348 * <h5 class='figure'>Examples:</h5> 349 * <p class='bcode w800'> 350 * <jc>// Direct value</jc> 351 * <ja>@RestResource</ja>(description=<js>"My Resource"</js>) 352 * <jk>public class</jk> MyResource {...} 353 * 354 * <jc>// Pulled from some other location</jc> 355 * <ja>@RestResource</ja>(description=<js>"$L{myLocalizedDescription}"</js>) 356 * <jk>public class</jk> MyResource {...} 357 * </p> 358 * <li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)} 359 * on the resource class, then any parent classes. 360 * <ol> 361 * <li><ck>[ClassName].description</ck> 362 * <li><ck>description</ck> 363 * </ol> 364 * <br>Value can contain any SVL variables defined on the {@link RestResource#description() @RestResource(description)} annotation. 365 * <h5 class='figure'>Examples:</h5> 366 * <p class='bcode w800'> 367 * <cc>// Direct value</cc> 368 * <ck>MyClass.description</ck> = <cv>My Resource</cv> 369 * 370 * <cc>// Pulled from some other location</cc> 371 * <ck>MyClass.description</ck> = <cv>$C{MyStrings/MyClass.description}</cv> 372 * </p> 373 * <li><ck>/info/description</ck> entry in swagger file. 374 * </ol> 375 * 376 * @param req The current request. 377 * @return The localized description of this REST resource, or <jk>null</jk> if none was was found. 378 * @throws Exception 379 */ 380 @Override /* RestInfoProvider */ 381 public String getDescription(RestRequest req) throws Exception { 382 VarResolverSession vr = req.getVarResolverSession(); 383 if (description != null) 384 return vr.resolve(description); 385 String description = context.getMessages().findFirstString(req.getLocale(), "description"); 386 if (description != null) 387 return vr.resolve(description); 388 return null; 389 } 390 391 //----------------------------------------------------------------------------------------------------------------- 392 // Utility methods 393 //----------------------------------------------------------------------------------------------------------------- 394 395 private Operation getSwaggerOperation(Method method, RestRequest req) throws Exception { 396 397 Swagger s = getSwagger(req); 398 if (s != null) { 399 Map<String,OperationMap> sp = s.getPaths(); 400 if (sp != null) { 401 Map<String,Operation> spp = sp.get(getAnnotation(RestMethod.class, method).path()); 402 if (spp != null) 403 return spp.get(req.getMethod()); 404 } 405 } 406 return null; 407 } 408 409 static String joinnl(String[] ss) { 410 if (ss.length == 0) 411 return ""; 412 return StringUtils.joinnl(ss).trim(); 413 } 414 415 /** 416 * @deprecated Unused. 417 */ 418 @SuppressWarnings("javadoc") 419 @Deprecated 420 public Contact getContact(RestRequest req) throws Exception { 421 return null; 422 } 423 424 /** 425 * @deprecated Unused. 426 */ 427 @SuppressWarnings("javadoc") 428 @Deprecated 429 public License getLicense(RestRequest req) throws Exception { 430 return null; 431 } 432 433 /** 434 * @deprecated Unused. 435 */ 436 @SuppressWarnings("javadoc") 437 @Deprecated 438 public String getTermsOfService(RestRequest req) throws Exception { 439 return null; 440 } 441 442 /** 443 * @deprecated Unused. 444 */ 445 @SuppressWarnings("javadoc") 446 @Deprecated 447 public String getVersion(RestRequest req) throws Exception { 448 return null; 449 } 450 451 /** 452 * @deprecated Unused. 453 */ 454 @SuppressWarnings("javadoc") 455 @Deprecated 456 public List<MediaType> getConsumes(RestRequest req) throws Exception { 457 return null; 458 } 459 460 /** 461 * @deprecated Unused. 462 */ 463 @SuppressWarnings("javadoc") 464 @Deprecated 465 public List<MediaType> getProduces(RestRequest req) throws Exception { 466 return null; 467 } 468 469 /** 470 * @deprecated Unused. 471 */ 472 @SuppressWarnings("javadoc") 473 @Deprecated 474 public List<Tag> getTags(RestRequest req) throws Exception { 475 return null; 476 } 477 478 /** 479 * @deprecated Unused. 480 */ 481 @SuppressWarnings("javadoc") 482 @Deprecated 483 public ExternalDocumentation getExternalDocs(RestRequest req) throws Exception { 484 return null; 485 } 486 487 /** 488 * @deprecated Unused. 489 */ 490 @SuppressWarnings("javadoc") 491 @Deprecated 492 public List<MediaType> getMethodConsumes(Method method, RestRequest req) throws Exception { 493 return null; 494 } 495 496 /** 497 * @deprecated Unused. 498 */ 499 @SuppressWarnings("javadoc") 500 @Deprecated 501 public ExternalDocumentation getMethodExternalDocs(Method method, RestRequest req) throws Exception { 502 return null; 503 } 504 505 /** 506 * @deprecated Unused. 507 */ 508 @SuppressWarnings("javadoc") 509 @Deprecated 510 public String getMethodOperationId(Method method, RestRequest req) throws Exception { 511 return null; 512 } 513 514 /** 515 * @deprecated Unused. 516 */ 517 @SuppressWarnings("javadoc") 518 @Deprecated 519 public List<ParameterInfo> getMethodParameters(Method method, RestRequest req) throws Exception { 520 return null; 521 } 522 523 /** 524 * @deprecated Unused. 525 */ 526 @SuppressWarnings("javadoc") 527 @Deprecated 528 public List<MediaType> getMethodProduces(Method method, RestRequest req) throws Exception { 529 return null; 530 } 531 532 /** 533 * @deprecated Unused. 534 */ 535 @SuppressWarnings("javadoc") 536 @Deprecated 537 public Map<Integer,ResponseInfo> getMethodResponses(Method method, RestRequest req) throws Exception { 538 return null; 539 } 540 541 /** 542 * @deprecated Unused. 543 */ 544 @SuppressWarnings("javadoc") 545 @Deprecated 546 public List<String> getMethodTags(Method method, RestRequest req) throws Exception { 547 return null; 548 } 549 550 /** 551 * @deprecated Unused. 552 */ 553 @SuppressWarnings("javadoc") 554 @Deprecated 555 public Swagger getSwaggerFromFile(RestRequest req) throws Exception { 556 return null; 557 } 558 559 /** 560 * @deprecated Unused. 561 */ 562 @SuppressWarnings("javadoc") 563 @Deprecated 564 public boolean isDeprecated(Method method, RestRequest req) throws Exception { 565 return false; 566 } 567}