Skip to content

refactor(swagger): change to using group name of swagger #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.satish.central.docs.config.swagger;

import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
Expand All @@ -11,37 +12,39 @@
import springfox.documentation.swagger.web.SwaggerResource;

/**
*
* @author satish sharma
* <pre>
* In-Memory store to hold API-Definition JSON
* </pre>
*/
@Component
@Scope(scopeName=ConfigurableBeanFactory.SCOPE_SINGLETON)
public class ServiceDefinitionsContext {

private final ConcurrentHashMap<String,String> serviceDescriptions;

private ServiceDefinitionsContext(){
serviceDescriptions = new ConcurrentHashMap<String, String>();
}

public void addServiceDefinition(String serviceName, String serviceDescription){
serviceDescriptions.put(serviceName, serviceDescription);
}

public String getSwaggerDefinition(String serviceId){
return this.serviceDescriptions.get(serviceId);
}

public List<SwaggerResource> getSwaggerDefinitions(){
return serviceDescriptions.entrySet().stream().map( serviceDefinition -> {
SwaggerResource resource = new SwaggerResource();
resource.setLocation("/service/"+serviceDefinition.getKey());
resource.setName(serviceDefinition.getKey());
resource.setSwaggerVersion("2.0");
return resource;
}).collect(Collectors.toList());
}

private final ConcurrentHashMap<String, String> serviceDescriptions;

private ServiceDefinitionsContext() {
serviceDescriptions = new ConcurrentHashMap<>();
}

public void addServiceDefinition(String serviceName, String serviceDescription) {
serviceDescriptions.put(serviceName.toUpperCase(), serviceDescription);
}

public String getSwaggerDefinition(String serviceId) {
return this.serviceDescriptions.get(serviceId);
}

public List<SwaggerResource> getSwaggerDefinitions() {
return serviceDescriptions.entrySet()
.parallelStream()
.map(service -> {
final SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setLocation("/service/" + service.getKey());
swaggerResource.setName(service.getKey());
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
})
.sorted(Comparator.comparing(SwaggerResource::getName))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,99 +1,127 @@
package com.satish.central.docs.config.swagger;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.http.HttpMethod.GET;

/**
*
* @author satish sharma
* <pre>
* Periodically poll the service instaces and update the in memory store as key value pair
* Periodically poll the service instaces and update the in memory store as key value pair
* </pre>
*/
@Slf4j
@Component
public class ServiceDescriptionUpdater {

private static final Logger logger = LoggerFactory.getLogger(ServiceDescriptionUpdater.class);

private static final String DEFAULT_SWAGGER_URL="/v2/api-docs";
private static final String KEY_SWAGGER_URL="swagger_url";

@Autowired
private DiscoveryClient discoveryClient;

private final RestTemplate template;

public ServiceDescriptionUpdater(){
this.template = new RestTemplate();
}

@Autowired
private ServiceDefinitionsContext definitionContext;

@Scheduled(fixedDelayString= "${swagger.config.refreshrate}")
public void refreshSwaggerConfigurations(){
logger.debug("Starting Service Definition Context refresh");

discoveryClient.getServices().stream().forEach(serviceId -> {
logger.debug("Attempting service definition refresh for Service : {} ", serviceId);
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);
if(serviceInstances == null || serviceInstances.isEmpty()){ //Should not be the case kept for failsafe
logger.info("No instances available for service : {} ",serviceId);
}else{
ServiceInstance instance = serviceInstances.get(0);
String swaggerURL = getSwaggerURL( instance);

Optional<Object> jsonData = getSwaggerDefinitionForAPI(serviceId, swaggerURL);

if(jsonData.isPresent()){
String content = getJSON(serviceId, jsonData.get());
definitionContext.addServiceDefinition(serviceId, content);
}else{
logger.error("Skipping service id : {} Error : Could not get Swagegr definition from API ",serviceId);
}

logger.info("Service Definition Context Refreshed at : {}",LocalDate.now());
}
});
}

private String getSwaggerURL( ServiceInstance instance){
String swaggerURL = instance.getMetadata().get(KEY_SWAGGER_URL);
return swaggerURL != null ? instance.getUri()+swaggerURL : instance.getUri()+DEFAULT_SWAGGER_URL;
}

private Optional<Object> getSwaggerDefinitionForAPI(String serviceName, String url){
logger.debug("Accessing the SwaggerDefinition JSON for Service : {} : URL : {} ", serviceName, url);
try{
Object jsonData = template.getForObject(url, Object.class);
return Optional.of(jsonData);
}catch(RestClientException ex){
logger.error("Error while getting service definition for service : {} Error : {} ", serviceName, ex.getMessage());
return Optional.empty();
}

}

public String getJSON(String serviceId, Object jsonData){
try {
return new ObjectMapper().writeValueAsString(jsonData);
} catch (JsonProcessingException e) {
logger.error("Error : {} ", e.getMessage());
return "";
}
}

private static final String SWAGGER_RESOURCES = "/swagger-resources";

@Autowired
private DiscoveryClient discoveryClient;

private final RestTemplate template;

public ServiceDescriptionUpdater() {
this.template = new RestTemplate();
}

@Autowired
private ServiceDefinitionsContext definitionContext;

@Scheduled(fixedDelayString = "${swagger.config.refreshrate}")
public void refreshSwaggerConfigurations() {
log.debug("Starting Service Definition Context refresh");

discoveryClient.getServices().stream().forEach(serviceId -> {

log.debug("Attempting service definition refresh for Service : {} ", serviceId);
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceId);

if (serviceInstances == null || serviceInstances.isEmpty()) { //Should not be the case kept for failsafe
log.info("No instances available for service : {} ", serviceId);
} else {
serviceInstances.stream()
.findFirst()
.ifPresent(instance -> {
getSwaggerURL(instance).parallelStream()
.forEach(swaggerResponse -> {
Optional<Object> jsonData = getSwaggerDefinitionForAPI(serviceId, instance, swaggerResponse.getLocation());
if (jsonData.isPresent()) {
String content = getJSON(serviceId, jsonData.get());
definitionContext.addServiceDefinition(serviceId + " - " + swaggerResponse.getName(), content);
} else {
log.error("Skipping service id : {} Error : Could not get Swagegr definition from API ", serviceId);
}
});
log.info("Service Definition Context Refreshed at : {}", LocalDate.now());
});
}
});
}

private Set<SwaggerResponse> getSwaggerURL(ServiceInstance instance) {
final String url = instance.getUri() + SWAGGER_RESOURCES;
try {

final ResponseEntity<List<SwaggerResponse>> response = template.exchange(url, GET, null, new ParameterizedTypeReference<List<SwaggerResponse>>() {
});

if (HttpStatus.OK.equals(response.getStatusCode())) {
return response.getBody().stream().collect(Collectors.toSet());
}
} catch (Exception e) {
log.error("Error while getting swagger definition for URL : {} Error : {} ", url, e.getMessage());
}
return Collections.EMPTY_SET;
}

private Optional<Object> getSwaggerDefinitionForAPI(String serviceName, ServiceInstance instance, String url) {
log.debug("Accessing the SwaggerDefinition JSON for Service : {} : URL : {} ", serviceName, url);
try {
Object jsonData = template.getForObject(instance.getUri() + url, Object.class);
return Optional.of(jsonData);
} catch (Exception ex) {
log.error("Error while getting service definition for service : {} Error : {} ", serviceName, ex.getMessage());
return Optional.empty();
}

}

public String getJSON(String serviceId, Object jsonData) {
try {
return new ObjectMapper().writeValueAsString(jsonData);
} catch (JsonProcessingException e) {
log.error("Error : {} ", e.getMessage());
return "";
}
}

@Getter
@Setter
public static class SwaggerResponse {
private String location, swaggerVersion, name, url;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,15 @@ public class SwaggerUIConfiguration {
private ServiceDefinitionsContext definitionContext;

@Bean
public RestTemplate configureTempalte(){
public RestTemplate createRestTemplate(){
return new RestTemplate();
}

@Primary

@Bean
@Lazy
public SwaggerResourcesProvider swaggerResourcesProvider(InMemorySwaggerResourcesProvider defaultResourcesProvider, RestTemplate temp) {
return () -> {
List<SwaggerResource> resources = new ArrayList<>(defaultResourcesProvider.get());
resources.clear();
resources.addAll(definitionContext.getSwaggerDefinitions());
return resources;
};
@Primary
public SwaggerResourcesProvider createSwaggerResourcesProvider() {
return () -> definitionContext.getSwaggerDefinitions();
}
}

Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.satish.central.docs.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.satish.central.docs.config.swagger.ServiceDefinitionsContext;

import java.util.Optional;

/**
*
* @author satish sharma
Expand All @@ -19,11 +22,11 @@ public class ServiceDefinitionController {

@Autowired
private ServiceDefinitionsContext definitionContext;
@GetMapping("/service/{servicename}")
public String getServiceDefinition(@PathVariable("servicename") String serviceName){

return definitionContext.getSwaggerDefinition(serviceName);


@GetMapping("/service/{serviceName}")
public ResponseEntity<?> getServiceDefinition(@PathVariable String serviceName){
return Optional.ofNullable(definitionContext.getSwaggerDefinition(serviceName))
.map(ResponseEntity::ok)
.orElseGet(ResponseEntity.notFound()::build);
}
}