From 720bb15544e493ca7644a4dce812bcbc0e56a1d1 Mon Sep 17 00:00:00 2001 From: Carlos Henrique Nonnemacher Date: Thu, 4 Apr 2019 12:36:41 -0300 Subject: [PATCH] refactor(swagger): change to using group name of swagger --- .../swagger/ServiceDefinitionsContext.java | 55 +++--- .../swagger/ServiceDescriptionUpdater.java | 186 ++++++++++-------- .../swagger/SwaggerUIConfiguration.java | 16 +- .../docs/web/ServiceDefinitionController.java | 15 +- 4 files changed, 151 insertions(+), 121 deletions(-) diff --git a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDefinitionsContext.java b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDefinitionsContext.java index 5751909..0925d67 100644 --- a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDefinitionsContext.java +++ b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDefinitionsContext.java @@ -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; @@ -11,37 +12,39 @@ import springfox.documentation.swagger.web.SwaggerResource; /** - * * @author satish sharma *
  *   	In-Memory store to hold API-Definition JSON
  * 
*/ @Component -@Scope(scopeName=ConfigurableBeanFactory.SCOPE_SINGLETON) public class ServiceDefinitionsContext { - - private final ConcurrentHashMap serviceDescriptions; - - private ServiceDefinitionsContext(){ - serviceDescriptions = new ConcurrentHashMap(); - } - - public void addServiceDefinition(String serviceName, String serviceDescription){ - serviceDescriptions.put(serviceName, serviceDescription); - } - - public String getSwaggerDefinition(String serviceId){ - return this.serviceDescriptions.get(serviceId); - } - - public List 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 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 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()); + } } diff --git a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDescriptionUpdater.java b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDescriptionUpdater.java index 3def0ea..5330dd8 100644 --- a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDescriptionUpdater.java +++ b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/ServiceDescriptionUpdater.java @@ -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 *
- *   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
  * 
*/ +@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 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 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 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 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 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 getSwaggerURL(ServiceInstance instance) { + final String url = instance.getUri() + SWAGGER_RESOURCES; + try { + + final ResponseEntity> response = template.exchange(url, GET, null, new ParameterizedTypeReference>() { + }); + + 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 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; + } + } diff --git a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/SwaggerUIConfiguration.java b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/SwaggerUIConfiguration.java index d0779a3..dfb6d85 100644 --- a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/SwaggerUIConfiguration.java +++ b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/config/swagger/SwaggerUIConfiguration.java @@ -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 resources = new ArrayList<>(defaultResourcesProvider.get()); - resources.clear(); - resources.addAll(definitionContext.getSwaggerDefinitions()); - return resources; - }; + @Primary + public SwaggerResourcesProvider createSwaggerResourcesProvider() { + return () -> definitionContext.getSwaggerDefinitions(); } } + diff --git a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/web/ServiceDefinitionController.java b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/web/ServiceDefinitionController.java index 1489054..0db4aa6 100644 --- a/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/web/ServiceDefinitionController.java +++ b/centralized-swagger-docs/documentation-app/src/main/java/com/satish/central/docs/web/ServiceDefinitionController.java @@ -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 @@ -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); } }