org.apache.juneau.rest.BasicRestInfoProvider Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.dto.swagger.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.svl.*;
/**
* Default implementation of {@link RestInfoProvider}.
*
*
* Subclasses can override these methods to tailor how HTTP REST resources are documented.
*
*
See Also:
*
* - {@link RestContext#REST_infoProvider}
*
- {@doc juneau-rest-server.Swagger}
*
*/
public class BasicRestInfoProvider implements RestInfoProvider {
private final RestContext context;
private final String
siteName,
title,
description;
private final ConcurrentHashMap> swaggers = new ConcurrentHashMap<>();
/**
* Constructor.
*
* @param context The resource context.
*/
public BasicRestInfoProvider(RestContext context) {
this.context = context;
Builder b = new Builder(context);
this.siteName = b.siteName;
this.title = b.title;
this.description = b.description;
}
private static final class Builder {
String
siteName,
title,
description;
Builder(RestContext context) {
for (RestResource r : getAnnotationsParentFirst(RestResource.class, context.getResource().getClass())) {
if (! r.siteName().isEmpty())
siteName = r.siteName();
if (r.title().length > 0)
title = joinnl(r.title());
if (r.description().length > 0)
description = joinnl(r.description());
}
}
}
/**
* Returns the localized swagger for this REST resource.
*
*
* Subclasses can override this method to customize the Swagger.
*
* @param req The incoming HTTP request.
* @return
* A new Swagger instance.
*
Never null .
* @throws Exception
*/
@Override /* RestInfoProvider */
public Swagger getSwagger(RestRequest req) throws Exception {
Locale locale = req.getLocale();
// Find it in the cache.
// Swaggers are cached by user locale and an int hash of the @RestMethods they have access to.
HashCode userHash = HashCode.create();
for (RestJavaMethod sm : context.getCallMethods().values())
if (sm.isRequestAllowed(req))
userHash.add(sm.hashCode());
int hashCode = userHash.get();
if (! swaggers.containsKey(locale))
swaggers.putIfAbsent(locale, new ConcurrentHashMap());
Swagger swagger = swaggers.get(locale).get(hashCode);
if (swagger != null)
return swagger;
// Wasn't cached...need to create one.
swagger = new SwaggerGenerator(req).getSwagger();
swaggers.get(locale).put(hashCode, swagger);
return swagger;
}
/**
* Returns the localized summary of the specified java method on this servlet.
*
*
* Subclasses can override this method to provide their own summary.
*
*
* The default implementation returns the value from the following locations (whichever matches first):
*
* - {@link RestMethod#summary() @RestMethod(summary)} annotation.
*
Examples:
*
* // Direct value
* @RestMethod (summary="Summary of my method" )
* public Object myMethod() {...}
*
* // Pulled from some other location
* @RestMethod (summary="$L{myLocalizedSummary}" )
* public Object myMethod() {...}
*
* - Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)}
* on the resource class, then any parent classes.
*
* [ClassName].[javaMethodName].summary
* [javaMethodName].summary
*
*
Value can contain any SVL variables defined on the {@link RestMethod#summary() @RestMethod(summary)} annotation.
* Examples:
*
* // Direct value
* MyClass.myMethod.summary = Summary of my method.
*
* // Pulled from some other location
* MyClass.myMethod.summary = $C{MyStrings/MyClass.myMethod.summary}
*
*
*
* @param method The Java method annotated with {@link RestMethod @RestMethod}.
* @param req The current request.
* @return The localized summary of the method, or null if none was found.
* @throws Exception
*/
@Override /* RestInfoProvider */
public String getMethodSummary(Method method, RestRequest req) throws Exception {
VarResolverSession vr = req.getVarResolverSession();
String s = method.getAnnotation(RestMethod.class).summary();
if (s.isEmpty()) {
Operation o = getSwaggerOperation(method, req);
if (o != null)
s = o.getSummary();
}
return isEmpty(s) ? null : vr.resolve(s);
}
/**
* Returns the localized description of the specified java method on this servlet.
*
*
* Subclasses can override this method to provide their own description.
*
*
* The default implementation returns the value from the following locations (whichever matches first):
*
* - {@link RestMethod#description() @RestMethod(description)} annotation.
*
Examples:
*
* // Direct value
* @RestMethod (description="Description of my method" )
* public Object myMethod() {...}
*
* // Pulled from some other location
* @RestMethod (description="$L{myLocalizedDescription}" )
* public Object myMethod() {...}
*
* - Localized string from resource bundle identified by {@link RestResource#messages() @RestResource(messages)}
* on the resource class, then any parent classes.
*
* [ClassName].[javaMethodName].description
* [javaMethodName].description
*
*
Value can contain any SVL variables defined on the {@link RestMethod#description() @RestMethod(description)} annotation.
* Examples:
*
* // Direct value
* MyClass.myMethod.description = Description of my method.
*
* // Pulled from some other location
* MyClass.myMethod.description = $C{MyStrings/MyClass.myMethod.description}
*
*
*
* @param method The Java method annotated with {@link RestMethod @RestMethod}.
* @param req The current request.
* @return The localized description of the method, or null if none was found.
* @throws Exception
*/
@Override /* RestInfoProvider */
public String getMethodDescription(Method method, RestRequest req) throws Exception {
VarResolverSession vr = req.getVarResolverSession();
String s = joinnl(method.getAnnotation(RestMethod.class).description());
if (s.isEmpty()) {
Operation o = getSwaggerOperation(method, req);
if (o != null)
s = o.getDescription();
}
return isEmpty(s) ? null : vr.resolve(s);
}
/**
* Returns the localized site name of this REST resource.
*
*
* Subclasses can override this method to provide their own site name.
*
*
* The default implementation returns the value from the following locations (whichever matches first):
*
* - {@link RestResource#siteName() @RestResource(siteName)} annotation on this class, and then any parent classes.
*
Examples:
*
* // Direct value
* @RestResource (siteName="My Site" )
* public class MyResource {...}
*
* // Pulled from some other location
* @RestResource (siteName="$L{myLocalizedSiteName}" )
* public class MyResource {...}
*
* - Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)}
* on the resource class, then any parent classes.
*
* [ClassName].siteName
* siteName
*
*
Value can contain any SVL variables defined on the {@link RestResource#siteName() @RestResource(siteName)} annotation.
* Examples:
*
* // Direct value
* MyClass.siteName = My Site
*
* // Pulled from some other location
* MyClass.siteName = $C{MyStrings/MyClass.siteName}
*
*
*
* @param req The current request.
* @return The localized site name of this REST resource, or null if none was found.
* @throws Exception
*/
@Override /* RestInfoProvider */
public String getSiteName(RestRequest req) throws Exception {
VarResolverSession vr = req.getVarResolverSession();
if (siteName != null)
return vr.resolve(siteName);
String siteName = context.getMessages().findFirstString(req.getLocale(), "siteName");
if (siteName != null)
return vr.resolve(siteName);
return null;
}
/**
* Returns the localized title of this REST resource.
*
*
* Subclasses can override this method to provide their own title.
*
*
* The default implementation returns the value from the following locations (whichever matches first):
*
* - {@link RestResource#title() @RestResource(siteName)} annotation on this class, and then any parent classes.
*
Examples:
*
* // Direct value
* @RestResource (title="My Resource" )
* public class MyResource {...}
*
* // Pulled from some other location
* @RestResource (title="$L{myLocalizedTitle}" )
* public class MyResource {...}
*
* - Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)}
* on the resource class, then any parent classes.
*
* [ClassName].title
* title
*
*
Value can contain any SVL variables defined on the {@link RestResource#title() @RestResource(title)} annotation.
* Examples:
*
* // Direct value
* MyClass.title = My Resource
*
* // Pulled from some other location
* MyClass.title = $C{MyStrings/MyClass.title}
*
* /info/title entry in swagger file.
*
*
* @param req The current request.
* @return The localized title of this REST resource, or null if none was found.
* @throws Exception
*/
@Override /* RestInfoProvider */
public String getTitle(RestRequest req) throws Exception {
VarResolverSession vr = req.getVarResolverSession();
if (title != null)
return vr.resolve(title);
String title = context.getMessages().findFirstString(req.getLocale(), "title");
if (title != null)
return vr.resolve(title);
return null;
}
/**
* Returns the localized description of this REST resource.
*
*
* Subclasses can override this method to provide their own description.
*
*
* The default implementation returns the value from the following locations (whichever matches first):
*
* - {@link RestResource#description() @RestResource(description)} annotation on this class, and then any parent classes.
*
Examples:
*
* // Direct value
* @RestResource (description="My Resource" )
* public class MyResource {...}
*
* // Pulled from some other location
* @RestResource (description="$L{myLocalizedDescription}" )
* public class MyResource {...}
*
* - Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource(messages)}
* on the resource class, then any parent classes.
*
* [ClassName].description
* description
*
*
Value can contain any SVL variables defined on the {@link RestResource#description() @RestResource(description)} annotation.
* Examples:
*
* // Direct value
* MyClass.description = My Resource
*
* // Pulled from some other location
* MyClass.description = $C{MyStrings/MyClass.description}
*
* /info/description entry in swagger file.
*
*
* @param req The current request.
* @return The localized description of this REST resource, or null if none was was found.
* @throws Exception
*/
@Override /* RestInfoProvider */
public String getDescription(RestRequest req) throws Exception {
VarResolverSession vr = req.getVarResolverSession();
if (description != null)
return vr.resolve(description);
String description = context.getMessages().findFirstString(req.getLocale(), "description");
if (description != null)
return vr.resolve(description);
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Utility methods
//-----------------------------------------------------------------------------------------------------------------
private Operation getSwaggerOperation(Method method, RestRequest req) throws Exception {
Swagger s = getSwagger(req);
if (s != null) {
Map sp = s.getPaths();
if (sp != null) {
Map spp = sp.get(method.getAnnotation(RestMethod.class).path());
if (spp != null)
return spp.get(req.getMethod());
}
}
return null;
}
static String joinnl(String[] ss) {
if (ss.length == 0)
return "";
return StringUtils.joinnl(ss).trim();
}
}