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 = getAnnotation(RestMethod.class, method).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(getAnnotation(RestMethod.class, method).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(getAnnotation(RestMethod.class, method).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}