How to add custom Springdoc resources to swagger-ui page - spring-boot

I'm not good at swagger and related libraries, so sorry if the title is confusing.
I have several services which provide their api as swagger documentation in json format and via swagger-ui. Next, I have a springboot service which is a proxy of the previously mentioned services and it provides combined api of all services (one can select exact service in a dropdown). It was implemented like this:
public class PropertyResourceProvider implements SwaggerResourcesProvider {
#Autowired
private SwaggerConfigProperties swaggerConfigProperties; // Just a mapping of config
#Override
public List<SwaggerResource> get() {
// Doc for local (proxy) service
val local = new SwaggerResource();
local.setName(title);
local.setUrl(swaggerLocal);
local.setSwaggerVersion(version);
// Add other services - specified in config
List<SwaggerResource> services = swaggerConfigProperties.getServices().stream().map(service -> {
String contextPath = service.get("contextPath");
String externalPath = SWAGGER_DOC_BASE_PATH + "/" + contextPath;
val resource = new SwaggerResource();
resource.setName(service.get("name"));
resource.setUrl(externalPath);
resource.setSwaggerVersion(service.get("version"));
return resource;
}).collect(Collectors.toList());
services.add(local);
return services; // Return combined API
}
Now, we're moving from springfox to springdoc and I can't use SwaggerResource and SwaggerResourcesProvider. How can I implements the same using springdoc?

Related

Springdoc GroupedOpenApi not following global parameters set with OperationCustomizer

When using GroupedOpenApi to define an API group, the common set of parameters that are added to every endpoint is not present in the parameters list.
Below are the respective codes
#Bean
public GroupedOpenApi v1Apis() {
return GroupedOpenApi.builder().group("v1 APIs")
// hide all v2 APIs
.pathsToExclude("/api/v2/**", "/v2/**")
// show all v1 APIs
.pathsToMatch("/api/v1/**", "/v1/**")
.build();
}
And the class to add the Standard Headers to all the endpoints
#Component
public class GlobalHeaderAdder implements OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
operation.addParametersItem(new Parameter().$ref("#/components/parameters/ClientID"));
operation.addSecurityItem(new SecurityRequirement().addList("Authorization"));
List<Parameter> parameterList = operation.getParameters();
if (parameterList!=null && !parameterList.isEmpty()) {
Collections.rotate(parameterList, 1);
}
return operation;
}
}
Actual Output
Expected Output
Workaround
Adding the paths to be included/excluded in the application properties file solves the error. But something at the code level will be much appreciated.
Attach the required OperationCustomizerobject while building the Api Group.
#Bean
public GroupedOpenApi v1Apis(GlobalHeaderAdder globalHeaderAdder) {
return GroupedOpenApi.builder().group("v1 APIs")
// hide all v2 APIs
.pathsToExclude("/api/v2/**", "/v2/**")
// show all v1 APIs
.pathsToMatch("/api/v1/**", "/v1/**")
.addOperationCustomizer(globalHeaderAdded)
.build();
}
Edit: Answer updated with reference to #Value not providing values from application properties Spring Boot
Alternative to add and load OperationCustomizer in the case you declare yours open api groups by properties springdoc.group-configs[0].group= instead definition by Java code in a Spring Configuration GroupedOpenApi.builder().
#Bean
public Map<String, GroupedOpenApi> configureGroupedsOpenApi(Map<String, GroupedOpenApi> groupedsOpenApi, OperationCustomizer operationCustomizer) {
groupedsOpenApi.forEach((id, groupedOpenApi) -> groupedOpenApi.getOperationCustomizers()
.add(operationCustomizer));
return groupedsOpenApi;
}

Looking for Matched CachePublicMetrics in spring-boot 2.1.9.release

I am using following classes in one of controllers of (spring-boot.1.5.12 release)
I am unable to find matching classes in spring 2.1.9 release.
The following is the code snippet
import org.springframework.boot.actuate.endpoint.CachePublicMetrics;
import org.springframework.boot.actuate.metrics.Metric;
public class CachingController extends CloudRestTemplate {
#Autowired
private CachePublicMetrics metrics;
public #ResponseBody Map<String, Object> getData(#Pattern(regexp=Constants.STRING_VALID_PATTERN, message=Constants.STRING_INVALID_MSG) #PathVariable(required = true) final String name) throws Exception {
boolean success = false;
Map<String, Object> m = Maps.newHashMap();
Collection<Metric<?>> resp = new ArrayList<>();
Collection<Metric<?>> mets = metrics.metrics();
for (Iterator<Metric<?>> iterator = mets.iterator(); iterator.hasNext();) {
Metric<?> met = iterator.next();
String metName = met.getName();
logger.debug(metName+":"+met.getValue());
if(StringUtils.isNotEmpty(metName)
&& metName.indexOf(name) != -1 ){
resp.add(met);
}
}
}
I think you should take a deeper look into Spring boot actuator, once you expose your all endpoints you might find what you are looking for. Spring Boot provides bunch of pre-defined endpoints, below is a list of Spring boot actuator endpoints (source)
/auditevents: Exposes audit events information for the current application.
/beans: Returns list of all spring beans in the application.
/caches: Gives information about the available caches.
/health: Provides applications health information.
/conditions: Provides list of conditions those were evaluated during auto configurations.
/configprops: Returns list of application level properties.
/info: Provides information about current application. This info can be configured in a properties file.
/loggers: Displays logging configurations. Moreover, this endpoint can be used to modify the configurations.
/headdump: Produces a head dump file and returns it.
/metrics: Returns various metrics about the application. Includes memory, heap and threads info. However, this endpoint doesn’t return any metrics. While, it only returns list of available metrics, the
metrics names can be used in a separate request to fetch the respective details. For instance, /actuator/metrics/jvm.memory.max like this.
/scheduledtasks: Returns a list of Scheduled tasks in the application.
/httptrace: Returns last 100 http interactions in the form of request and response. Including, the actuator endpoints.
/mappings: List of all Http Request Mappings. Also includes the actuator endpoints.
Edit:
As discussed in comments, you need to access /actuator/metrics/jvm.memory.max , you can invoke same using RestTemplate if you want to access using Java, you need to explore Actuator APIs, I wrote a quick program, you can refer the same
#Autowired
private MetricsEndpoint metricsEndpoint;
public MetricResponse printJavaMaxMemMetrics() {
ListNamesResponse listNames = metricsEndpoint.listNames();
listNames.getNames().stream().forEach(name -> System.out.println(name));
MetricResponse metric = metricsEndpoint.metric("jvm.memory.max", new ArrayList<>());
System.out.println("metric (jvm.memory.max) ->" + metric);
List<AvailableTag> availableTags = metric.getAvailableTags();
availableTags.forEach(tag -> System.out.println(tag.getTag() + " : " + tag.getValues()));
List<Sample> measurements = metric.getMeasurements();
measurements.forEach(sample -> System.out.println(sample.getStatistic() + " : " + sample.getValue()));
return metric;
}

Feign with RibbonClient and Consul discovery without Spring Cloud

I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.
In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.

Spring Boot Actuator: have a customized status as plain text?

I'm trying to integrate Spring Boot Actuator with my companies existing infrastructure. To do this I need to be able to customize the status message. For instance if the app is up and running correctly I need to return a 200 and a plain text body of "HAPPY" from the health actuator endpoint.
Is such customization currently possible? Since the Status class is final I can't extend it, but I think that would work.
Spring Boot uses a HealthAggregator to aggregate all of the statuses from the individual health indicators into a single health for the entire application. You can plug in a custom aggregator that delegates to Boot's default aggregator, OrderedHealthAggregator, and then maps UP to HAPPY:
#Bean
public HealthAggregator healthAggregator() {
return new HappyHealthAggregator(new OrderedHealthAggregator());
}
static class HappyHealthAggregator implements HealthAggregator {
private final HealthAggregator delegate;
HappyHealthAggregator(HealthAggregator delegate) {
this.delegate = delegate;
}
#Override
public Health aggregate(Map<String, Health> healths) {
Health result = this.delegate.aggregate(healths);
if (result.getStatus() == Status.UP) {
return new Health.Builder(new Status("HAPPY"), result.getDetails())
.build();
}
return result;
}
}
If you want to take complete control over the format of the response, then you'll need to write your own MVC endpoint implementation. You could use the existing HealthMvcEndpointclass in Spring Boot as a super class and override its invoke method.

how to change the #FeignClient name in runtime

I use Spring Cloud Netflix to build my micro service .
#FeignClient(name = "ms-cloud",configuration = MsCloudClientConfig.class)
public interface TestClient {
/**
* #return
*/
#RequestMapping(value = "/test", method = RequestMethod.GET)
String test();
}
I want to change the name to ms-cloud-pre when some special user.
Anyone can give some advice?
According to the documentation feign supports placeholders in the name and url fields.
#FeignClient(name = "${store.name}")
public interface StoreClient {
//..
}
So you could set store.name=storeProd at runtime using normal spring boot configuration mechanisms.
To create a spring-cloud Feign client at runtime in situations where you don't know the service-id until the point of call:
import org.springframework.cloud.openfeign.FeignClientBuilder;
#Component
public class InfoFeignClient {
interface InfoCallSpec {
#RequestMapping(value = "/actuator/info", method = GET)
String info();
}
FeignClientBuilder feignClientBuilder;
public InfoFeignClient(#Autowired ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public String getInfo(String serviceId) {
InfoCallSpec spec =
this.feignClientBuilder.forType(InfoCallSpec.class, serviceId).build();
return spec.info();
}
}
That actually is possible. In Spring Cloud Zookeeper we're doing a similar thing since the name of the service in the Feign client is not the one that is there in the in Zookeeper. It can be an alias presented in the yaml file. Here you have the code example https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java#L54 and here you have the description of the dependencies feature - https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc#using-the-zookeeper-dependencies

Resources