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