org.apache.juneau.rest.RestInfoProvider 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 javax.servlet.http.HttpServletResponse.*;
import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
import static org.apache.juneau.internal.ReflectionUtils.*;
import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.dto.swagger.*;
import org.apache.juneau.http.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.svl.*;
/**
* Class that provides documentation and other related information about a REST resource.
*
*
* Subclasses can override these methods to tailor how HTTP REST resources are documented.
* Subclasses MUST implement a public constructor that takes in a {@link RestContext} object.
*
*
* RestInfoProviders are associated with servlets/resources in one of the following ways:
*
* - The {@link RestResource#infoProvider @RestResource.infoProvider()} annotation.
*
- The {@link RestConfig#setInfoProvider(Class)}/{@link RestConfig#setInfoProvider(RestInfoProvider)} methods.
*
*/
public class RestInfoProvider {
private final RestContext context;
private final String
siteName,
title,
description,
termsOfService,
contact,
license,
version,
tags,
externalDocs;
private final ConcurrentHashMap swaggers = new ConcurrentHashMap();
/**
* Constructor.
*
* @param context The resource context.
*/
public RestInfoProvider(RestContext context) {
this.context = context;
Builder b = new Builder(context);
this.siteName = b.siteName;
this.title = b.title;
this.description = b.description;
this.termsOfService = b.termsOfService;
this.contact = b.contact;
this.license = b.license;
this.version = b.version;
this.tags = b.tags;
this.externalDocs = b.externalDocs;
}
private static class Builder {
private String
siteName,
title,
description,
termsOfService,
contact,
license,
version,
tags,
externalDocs;
Builder(RestContext context) {
LinkedHashMap,RestResource> restResourceAnnotationsParentFirst = findAnnotationsMapParentFirst(RestResource.class, context.getResource().getClass());
for (RestResource r : restResourceAnnotationsParentFirst.values()) {
if (! r.siteName().isEmpty())
siteName = r.siteName();
if (! r.title().isEmpty())
title = r.title();
if (! r.description().isEmpty())
description = r.description();
ResourceSwagger sr = r.swagger();
if (! sr.termsOfService().isEmpty())
termsOfService = sr.termsOfService();
if (! sr.contact().isEmpty())
contact = sr.contact();
if (! sr.license().isEmpty())
license = sr.license();
if (! sr.version().isEmpty())
version = sr.version();
if (! sr.tags().isEmpty())
tags = sr.tags();
if (! sr.externalDocs().isEmpty())
externalDocs = sr.externalDocs();
}
}
}
/**
* Returns the localized swagger for this REST resource.
*
* @param req The incoming HTTP request.
* @return A new Swagger instance.
* @throws RestException
*/
protected Swagger getSwagger(RestRequest req) throws RestException {
try {
// If a file is defined, use that.
Swagger s = req.getSwaggerFromFile();
if (s != null)
return s;
s = swagger(
info(getTitle(req), getVersion(req))
.contact(getContact(req))
.license(getLicense(req))
.description(getDescription(req))
.termsOfService(getTermsOfService(req))
)
.consumes(context.getSupportedAcceptTypes())
.produces(context.getSupportedContentTypes())
.tags(getTags(req))
.externalDocs(getExternalDocs(req));
for (CallMethod sm : context.getCallMethods().values()) {
if (sm.isRequestAllowed(req)) {
Operation o = sm.getSwaggerOperation(req);
s.path(
sm.getPathPattern(),
sm.getHttpMethod().toLowerCase(),
o
);
}
}
return s;
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
/**
* Returns the localized Swagger from the file system.
*
*
* Looks for a file called "{ServletClass}_{locale}.json" in the same package as this servlet and returns
* it as a parsed {@link Swagger} object.
*
*
* Returned objects are cached for later quick-lookup.
*
* @param locale The locale of the swagger.
* @return The parsed swagger object, or null if the swagger file could not be found.
* @throws RestException
*/
protected Swagger getSwaggerFromFile(Locale locale) throws RestException {
Swagger s = swaggers.get(locale);
if (s == null) {
try {
s = context.getResource(Swagger.class, MediaType.JSON, getClass().getSimpleName() + ".json", locale);
swaggers.putIfAbsent(locale, s == null ? Swagger.NULL : s);
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
return s == Swagger.NULL ? null : s;
}
/**
* 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 summary from the following locations (whichever matches first):
*
* - {@link RestMethod#summary() @RestMethod.summary()} annotation on the method.
*
[ClassName].[javaMethodName].summary property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* [javaMethodName].summary property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
*
*
* @param javaMethodName The name of the Java method whose description we're retrieving.
* @param req The current request.
* @return The localized summary of the method, or a blank string if no summary was found.
*/
public String getMethodSummary(String javaMethodName, RestRequest req) {
CallMethod m = context.getCallMethods().get(javaMethodName);
if (m != null)
return m.getSummary(req);
return "";
}
/**
* 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 description from the following locations (whichever matches first):
*
* - {@link RestMethod#description() @RestMethod.description()} annotation on the method.
*
[ClassName].[javaMethodName].description property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* [javaMethodName].description property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
*
*
* @param javaMethodName The name of the Java method whose description we're retrieving.
* @param req The current request.
* @return The localized description of the method, or a blank string if no description was found.
*/
protected String getMethodDescription(String javaMethodName, RestRequest req) {
CallMethod m = context.getCallMethods().get(javaMethodName);
if (m != null)
return m.getDescription(req);
return "";
}
/**
* 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 description from the following locations (whichever matches first):
*
* - {@link RestResource#siteName() @RestResource.siteName()} annotation on this class, and then any parent classes.
*
[ClassName].siteName property in resource bundle identified by
* {@link RestResource#messages() @ResourceBundle.messages()} annotation for this class, then any parent
* classes.
* siteName in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
* annotation for this class, then any parent classes.
*
*
* @param req The current request.
* @return The localized description of this REST resource, or null if no resource description was found.
*/
public String getSiteName(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
if (this.siteName != null)
return vr.resolve(this.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 description from the following locations (whichever matches first):
*
* - {@link RestResource#title() @RestResource.title()} annotation on this class, and then any parent classes.
*
[ClassName].title property in resource bundle identified by
* {@link RestResource#messages() @ResourceBundle.messages()} annotation for this class, then any parent
* classes.
* title in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
* annotation for this class, then any parent classes.
* /info/title entry in swagger file.
*
*
* @param req The current request.
* @return The localized description of this REST resource, or null if no resource description was found.
*/
public String getTitle(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
if (this.title != null)
return vr.resolve(this.title);
String title = context.getMessages().findFirstString(req.getLocale(), "title");
if (title != null)
return vr.resolve(title);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getTitle();
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 description from the following locations (whichever matches first):
*
* - {@link RestResource#description() @RestResource.description()} annotation on this class, and then any
* parent classes.
*
[ClassName].description property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* description property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/description entry in swagger file.
*
*
* @param req The current request.
* @return The localized description of this REST resource, or null if no resource description was found.
*/
public String getDescription(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
if (this.description != null)
return vr.resolve(this.description);
String description = context.getMessages().findFirstString(req.getLocale(), "description");
if (description != null)
return vr.resolve(description);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getDescription();
return null;
}
/**
* Returns the localized contact information of this REST resource.
*
*
* Subclasses can override this method to provide their own contact information.
*
*
* The default implementation returns the contact information from the following locations (whichever matches first):
*
* - {@link ResourceSwagger#contact() @ResourceSwagger.contact()} annotation on this class, and then any parent
* classes.
*
[ClassName].contact property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* contact property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/contact entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public Contact getContact(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
JsonParser jp = JsonParser.DEFAULT;
try {
if (this.contact != null)
return jp.parse(vr.resolve(this.contact), Contact.class);
String contact = context.getMessages().findFirstString(req.getLocale(), "contact");
if (contact != null)
return jp.parse(vr.resolve(contact), Contact.class);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getContact();
return null;
} catch (ParseException e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
/**
* Returns the localized license information of this REST resource.
*
*
* Subclasses can override this method to provide their own license information.
*
*
* The default implementation returns the license information from the following locations (whichever matches first):
*
* - {@link ResourceSwagger#license() @ResourceSwagger.license()} annotation on this class, and then any parent
* classes.
*
[ClassName].license property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* license property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/license entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public License getLicense(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
JsonParser jp = JsonParser.DEFAULT;
try {
if (this.license != null)
return jp.parse(vr.resolve(this.license), License.class);
String license = context.getMessages().findFirstString(req.getLocale(), "license");
if (license != null)
return jp.parse(vr.resolve(license), License.class);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getLicense();
return null;
} catch (ParseException e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
/**
* Returns the terms-of-service information of this REST resource.
*
*
* Subclasses can override this method to provide their own terms-of-service information.
*
*
* The default implementation returns the terms-of-service information from the following locations (whichever
* matches first):
*
* - {@link ResourceSwagger#termsOfService() @ResourceSwagger.termsOfService()} annotation on this class, and
* then any parent classes.
*
[ClassName].termsOfService property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* termsOfService property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/termsOfService entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public String getTermsOfService(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
if (this.termsOfService != null)
return vr.resolve(this.termsOfService);
String termsOfService = context.getMessages().findFirstString(req.getLocale(), "termsOfService");
if (termsOfService != null)
return vr.resolve(termsOfService);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getTermsOfService();
return null;
}
/**
* Returns the version information of this REST resource.
*
*
* Subclasses can override this method to provide their own version information.
*
*
* The default implementation returns the version information from the following locations (whichever matches first):
*
* - {@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
* classes.
*
[ClassName].version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/version entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public String getVersion(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
if (this.version != null)
return vr.resolve(this.version);
String version = context.getMessages().findFirstString(req.getLocale(), "version");
if (version != null)
return vr.resolve(version);
Swagger s = req.getSwaggerFromFile();
if (s != null && s.getInfo() != null)
return s.getInfo().getVersion();
return null;
}
/**
* Returns the version information of this REST resource.
*
*
* Subclasses can override this method to provide their own version information.
*
*
* The default implementation returns the version information from the following locations (whichever matches first):
*
* - {@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
* classes.
*
[ClassName].version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/version entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public List getTags(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
JsonParser jp = JsonParser.DEFAULT;
try {
if (this.tags != null)
return jp.parse(vr.resolve(this.tags), ArrayList.class, Tag.class);
String tags = context.getMessages().findFirstString(req.getLocale(), "tags");
if (tags != null)
return jp.parse(vr.resolve(tags), ArrayList.class, Tag.class);
Swagger s = req.getSwaggerFromFile();
if (s != null)
return s.getTags();
return null;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
/**
* Returns the version information of this REST resource.
*
*
* Subclasses can override this method to provide their own version information.
*
*
* The default implementation returns the version information from the following locations (whichever matches first):
*
* - {@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
* classes.
*
[ClassName].version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* version property in resource bundle identified by
* {@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
* /info/version entry in swagger file.
*
*
* @param req The current request.
* @return
* The localized contact information of this REST resource, or null if no contact information was found.
*/
public ExternalDocumentation getExternalDocs(RestRequest req) {
VarResolverSession vr = req.getVarResolverSession();
JsonParser jp = JsonParser.DEFAULT;
try {
if (this.externalDocs != null)
return jp.parse(vr.resolve(this.externalDocs), ExternalDocumentation.class);
String externalDocs = context.getMessages().findFirstString(req.getLocale(), "externalDocs");
if (externalDocs != null)
return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
Swagger s = req.getSwaggerFromFile();
if (s != null)
return s.getExternalDocs();
return null;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
}
}
}