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