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}