From bf11d9f82f4c5fb1f426b8235e6dc480a9f09856 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Fri, 15 Jul 2022 10:09:53 -0400 Subject: [PATCH 01/18] Send full ApiInfos to UI Signed-off-by: Carson Cook --- .../apiml/apicatalog/model/APIService.java | 23 +++----- .../cached/CachedProductFamilyService.java | 33 +++++------- .../CachedProductFamilyServiceTest.java | 52 +++++++++---------- api-catalog-ui/frontend/package.json | 2 +- .../components/ServiceTab/InstanceInfo.jsx | 12 ++--- 5 files changed, 51 insertions(+), 71 deletions(-) diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java index 89fa910c3d..4a5e868c28 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.zowe.apiml.config.ApiInfo; import java.io.Serializable; import java.util.ArrayList; @@ -63,11 +64,8 @@ public class APIService implements Serializable { @Schema(description = "The SSO support for all instances") private boolean ssoAllInstances; - @Schema(description = "The API ID for this service") - private Map apiId; - - @Schema(description = "The Gateway URLs used within this service") - private Map gatewayUrls; + @Schema(description = "The API information for each API ID for this service") + private Map apis; private List instances = new ArrayList<>(); @@ -77,7 +75,7 @@ private APIService(String serviceId) { } public static class Builder { - private APIService apiService; + private final APIService apiService; public Builder(String serviceId) { apiService = new APIService(serviceId); @@ -129,18 +127,13 @@ public Builder sso(boolean sso) { return this; } - public Builder apiId(Map apiId) { - apiService.apiId = apiId; - return this; - } - - public Builder gatewayUrls(Map gatewayUrls) { - apiService.gatewayUrls = gatewayUrls; + public Builder instanceId(String id) { + apiService.instances.add(id); return this; } - public Builder instanceId(String id) { - apiService.instances.add(id); + public Builder apis(Map apis) { + apiService.apis = apis; return this; } diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java index 60324c5f02..1154781d67 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java @@ -130,7 +130,7 @@ public APIContainer saveContainerFromInstance(String productFamilyId, InstanceIn } else { Set apiServices = container.getServices(); APIService service = createAPIServiceFromInstance(instanceInfo); - + // Verify whether already exists if (apiServices.contains(service)) { apiServices.stream() @@ -139,7 +139,7 @@ public APIContainer saveContainerFromInstance(String productFamilyId, InstanceIn if (!existingService.getInstances().contains(instanceInfo.getInstanceId())) { existingService.getInstances().add(instanceInfo.getInstanceId()); } - }); // If the instance is in list, do nothing otherwise + }); // If the instance is in list, do nothing otherwise } else { apiServices.add(service); } @@ -166,7 +166,7 @@ public APIContainer saveContainerFromInstance(String productFamilyId, InstanceIn * 1) it will remove the whole APIContainer (Tile) if there is no instance of any service remaining * 2) Remove the service from the containe if there is no instance of service remaining * 3) Remove instance from the service - * + * * @param removedInstanceFamilyId the product family id of the container * @param removedInstance the service instance */ @@ -219,7 +219,7 @@ public void calculateContainerServiceValues(APIContainer apiContainer) { boolean isSso = servicesCount > 0; for (APIService apiService : apiContainer.getServices()) { if (update(apiService)) { - activeServicesCount ++; + activeServicesCount++; } isSso &= apiService.isSsoAllInstances(); } @@ -333,22 +333,14 @@ private APIService createAPIServiceFromInstance(InstanceInfo instanceInfo) { String instanceHomePage = getInstanceHomePageUrl(instanceInfo); String apiBasePath = getApiBasePath(instanceInfo); - Map apiId = new HashMap<>(); - Map gatewayUrls = new HashMap<>(); + Map apiInfoById = new HashMap<>(); + try { - apiId = metadataParser.parseApiInfo(instanceInfo.getMetadata()).stream().filter(apiInfo -> apiInfo.getApiId() != null).collect( - Collectors.toMap( - apiInfo -> (apiInfo.getMajorVersion() < 0) ? "default" : apiInfo.getApiId() + " v" + apiInfo.getVersion(), - ApiInfo::getApiId - ) - ); - - gatewayUrls = metadataParser.parseApiInfo(instanceInfo.getMetadata()).stream().filter(apiInfo -> apiInfo.getApiId() != null).collect( - Collectors.toMap( - apiInfo -> (apiInfo.getMajorVersion() < 0) ? "default" : apiInfo.getApiId() + " v" + apiInfo.getVersion(), - ApiInfo::getGatewayUrl - ) - ); + List apiInfoList = metadataParser.parseApiInfo(instanceInfo.getMetadata()); + apiInfoList.stream().filter(apiInfo -> apiInfo.getApiId() != null).forEach(apiInfo -> { + String id = (apiInfo.getMajorVersion() < 0) ? "default" : apiInfo.getApiId() + " v" + apiInfo.getVersion(); + apiInfoById.put(id, apiInfo); + }); } catch (Exception ex) { log.info("createApiServiceFromInstance#incorrectVersions {}", ex.getMessage()); } @@ -361,8 +353,7 @@ private APIService createAPIServiceFromInstance(InstanceInfo instanceInfo) { .homePageUrl(instanceHomePage) .basePath(apiBasePath) .sso(isSso(instanceInfo)) - .apiId(apiId) - .gatewayUrls(gatewayUrls) + .apis(apiInfoById) .instanceId(instanceInfo.getInstanceId()) .build(); } diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java index 843a399bbf..7f8badf485 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java @@ -83,7 +83,7 @@ void prepareInstances() throws URLTransformationException { metadata.put(SERVICE_DESCRIPTION, "sDescription"); instance = servicesBuilder.createInstance("service1", InstanceInfo.InstanceStatus.UP, metadata); - Map updatedMetadata = new HashMap(); + Map updatedMetadata = new HashMap<>(); updatedMetadata.put(CATALOG_ID, "demoapp"); updatedMetadata.put(CATALOG_TITLE, "Title2"); updatedMetadata.put(CATALOG_DESCRIPTION, "Description2"); @@ -101,7 +101,7 @@ void prepareInstances() throws URLTransformationException { @Nested class GivenInstanceIsNotInCache { @Test - void createNew() throws URLTransformationException { + void createNew() { APIContainer originalContainer = underTest.saveContainerFromInstance("demoapp", instance); List lsContainer = underTest.getRecentlyUpdatedContainers(); @@ -112,7 +112,7 @@ void createNew() throws URLTransformationException { @Nested class GivenInstanceIsInTheCache { @Test - void update() throws InterruptedException, URLTransformationException { + void update() throws InterruptedException { APIContainer originalContainer = underTest.saveContainerFromInstance("demoapp", instance); Calendar createdTimestamp = originalContainer.getLastUpdatedTimestamp(); @@ -151,7 +151,7 @@ private void assertThatMetadataAreCorrect(APIContainer result, Map
@@ -31,11 +31,7 @@ export default class InstanceInfo extends Component {
- + {/* eslint-disable-next-line jsx-a11y/label-has-for */} From 4e01c00d9d6fe86af57ae23d33ad5ef5d78f05db Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Fri, 15 Jul 2022 11:41:17 -0400 Subject: [PATCH 02/18] Fix unit tests Signed-off-by: Carson Cook --- .../cached/CachedProductFamilyService.java | 6 ++- .../api/ApiCatalogControllerTests.java | 14 +++---- .../CachedProductFamilyServiceTest.java | 42 +++++++++---------- .../status/ApiServiceStatusServiceTest.java | 4 +- .../util/ContainerServiceMockUtil.java | 2 +- api-catalog-ui/frontend/package.json | 2 +- .../components/ServiceTab/InstanceInfo.jsx | 3 +- .../ServiceTab/InstanceInfo.test.jsx | 12 +++--- .../src/components/Swagger/SwaggerUI.test.jsx | 12 +++--- 9 files changed, 51 insertions(+), 46 deletions(-) diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java index 1154781d67..0b4873d986 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java @@ -31,7 +31,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static org.zowe.apiml.constants.EurekaMetadataDefinition.*; @@ -341,6 +340,11 @@ private APIService createAPIServiceFromInstance(InstanceInfo instanceInfo) { String id = (apiInfo.getMajorVersion() < 0) ? "default" : apiInfo.getApiId() + " v" + apiInfo.getVersion(); apiInfoById.put(id, apiInfo); }); + + if (!apiInfoById.containsKey("default")) { + ApiInfo defaultApiInfo = apiInfoList.stream().filter(ApiInfo::isDefaultApi).findFirst().orElse(null); + apiInfoById.put("default", defaultApiInfo); + } } catch (Exception ex) { log.info("createApiServiceFromInstance#incorrectVersions {}", ex.getMessage()); } diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/controllers/api/ApiCatalogControllerTests.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/controllers/api/ApiCatalogControllerTests.java index d202ba82cf..c4a7a91ec9 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/controllers/api/ApiCatalogControllerTests.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/controllers/api/ApiCatalogControllerTests.java @@ -61,7 +61,7 @@ class WhenAllContainersAreRequested { @Test void thenReturnNoContent() { given(cachedProductFamilyService.getAllContainers()).willReturn(null); - + RestAssuredMockMvc.given(). when(). get(pathToContainers). @@ -69,7 +69,7 @@ void thenReturnNoContent() { statusCode(HttpStatus.NO_CONTENT.value()); } } - + @Nested class WhenSpecificContainerRequested { @Test @@ -103,8 +103,8 @@ void prepareApplications() { apiVersions = Arrays.asList("1.0.0", "2.0.0"); given(cachedServicesService.getService("service1")).willReturn(service1); - given(cachedApiDocService.getDefaultApiDocForService("service1")).willReturn("service1"); - given(cachedApiDocService.getApiVersionsForService("service1")).willReturn(apiVersions); + given(cachedApiDocService.getDefaultApiDocForService("service1")).willReturn("service1"); + given(cachedApiDocService.getApiVersionsForService("service1")).willReturn(apiVersions); given(cachedServicesService.getService("service2")).willReturn(service2); given(cachedApiDocService.getDefaultApiDocForService("service2")).willReturn("service2"); @@ -193,7 +193,7 @@ private void assertThereIsOneContainer(ResponseEntity> contai } } - + // =========================================== Helper Methods =========================================== @@ -209,7 +209,7 @@ private List createContainers() { .homePageUrl("home") .basePath("base") .sso(false) - .apiId(Collections.emptyMap()) + .apis(Collections.emptyMap()) .build(); services.add(service); @@ -221,7 +221,7 @@ private List createContainers() { .homePageUrl("home") .basePath("base") .sso(false) - .apiId(Collections.emptyMap()) + .apis(Collections.emptyMap()) .build(); services.add(service); diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java index 7f8badf485..4b17845904 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyServiceTest.java @@ -383,27 +383,27 @@ void containerStatusIsWarning() { @Nested class GivenMultipleApiIds { -// @Test -// void groupThem() { -// Application application = servicesBuilder.createApp( -// SERVICE_ID, -// servicesBuilder.createInstance(SERVICE_ID, "catalog1", -// Pair.of("apiml.apiInfo.api-v1.apiId", "api1"), -// Pair.of("apiml.apiInfo.api-v1.version", "1.0.0"), -// Pair.of("apiml.apiInfo.api-v2.apiId", "api2"), -// Pair.of("apiml.apiInfo.api-v2.version", "2"), -// Pair.of("apiml.apiInfo.api-v3.apiId", "api3"))); -// doReturn(application).when(cachedServicesService).getService(SERVICE_ID); -// APIContainer apiContainer = underTest.getContainerById(SERVICE_ID); -// underTest.calculateContainerServiceValues(apiContainer); -// -// APIService apiService = apiContainer.getServices().iterator().next(); -// assertNotNull(apiService.getApiId()); -// assertEquals(3, apiService.getApiId().size()); -// assertEquals("api1", apiService.getApiId().get("api1 v1.0.0")); -// assertEquals("api2", apiService.getApiId().get("api2 v2")); -// assertEquals("api3", apiService.getApiId().get("default")); -// } + @Test + void groupThem() { + Application application = servicesBuilder.createApp( + SERVICE_ID, + servicesBuilder.createInstance(SERVICE_ID, "catalog1", + Pair.of("apiml.apiInfo.api-v1.apiId", "api1"), + Pair.of("apiml.apiInfo.api-v1.version", "1.0.0"), + Pair.of("apiml.apiInfo.api-v2.apiId", "api2"), + Pair.of("apiml.apiInfo.api-v2.version", "2"), + Pair.of("apiml.apiInfo.api-v3.apiId", "api3"))); + doReturn(application).when(cachedServicesService).getService(SERVICE_ID); + APIContainer apiContainer = underTest.getContainerById(SERVICE_ID); + underTest.calculateContainerServiceValues(apiContainer); + + APIService apiService = apiContainer.getServices().iterator().next(); + assertNotNull(apiService.getApis()); + assertEquals(3, apiService.getApis().size()); + assertNotNull(apiService.getApis().get("api1 v1.0.0")); + assertNotNull(apiService.getApis().get("api2 v2")); + assertNotNull(apiService.getApis().get("default")); + } } @Nested diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/status/ApiServiceStatusServiceTest.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/status/ApiServiceStatusServiceTest.java index 09180c3bd0..c4198dacf8 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/status/ApiServiceStatusServiceTest.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/services/status/ApiServiceStatusServiceTest.java @@ -172,7 +172,7 @@ private List createContainers() { .homePageUrl("home") .basePath("base") .sso(false) - .apiId(Collections.emptyMap()) + .apis(Collections.emptyMap()) .build(); services.add(service); @@ -184,7 +184,7 @@ private List createContainers() { .homePageUrl("home") .basePath("base") .sso(false) - .apiId(Collections.emptyMap()) + .apis(Collections.emptyMap()) .build(); services.add(service); diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/util/ContainerServiceMockUtil.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/util/ContainerServiceMockUtil.java index 253afde7a6..3b265bef6f 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/util/ContainerServiceMockUtil.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/util/ContainerServiceMockUtil.java @@ -115,7 +115,7 @@ private APIService addApiService(String serviceId, .homePageUrl("home") .basePath("base") .sso(false) - .apiId(Collections.emptyMap()) + .apis(Collections.emptyMap()) .build(); services.add(service); allServices.add(service); diff --git a/api-catalog-ui/frontend/package.json b/api-catalog-ui/frontend/package.json index 3228f86ce3..9ba5b9a4f5 100644 --- a/api-catalog-ui/frontend/package.json +++ b/api-catalog-ui/frontend/package.json @@ -54,7 +54,7 @@ "preinstall": "npm-force-resolutions", "build": "react-scripts build", "postbuild": "rimraf build/**/*.map", - "test": "echo pass", + "test": "react-scripts test --silent --watchAll=false --env=jsdom components/* utils/* reducers/* epics/* actions/* selectors/* ErrorBoundary/* helpers/* --reporters=default --reporters=jest-html-reporter --coverage", "cy:open": "cypress open", "cy:e2e:ci": "cypress run --spec \"cypress/integration/e2e/**/*.test.js\" --env catalogHomePage=https://gateway-service:10010/apicatalog/ui/v1 --browser chrome --headless", "cy:e2e:localhost": "cypress run --spec \"cypress/integration/e2e/**/*.test.js\" --browser chrome --headless", diff --git a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx index 74f253ef54..49f1ed16f5 100644 --- a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx +++ b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx @@ -15,7 +15,8 @@ import Shield from '../ErrorBoundary/Shield/Shield'; export default class InstanceInfo extends Component { render() { const { selectedService, selectedVersion } = this.props; - + console.log(selectedVersion); // eslint-disable-line no-console + console.log(selectedService); // eslint-disable-line no-console const apiInfo = selectedService.apis[selectedVersion || selectedService.defaultApiVersion] || selectedService.apis.default; const { apiId } = apiInfo; diff --git a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.test.jsx b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.test.jsx index a1342660d4..10b915d0c3 100644 --- a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.test.jsx +++ b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.test.jsx @@ -20,8 +20,8 @@ const selectedService = { describe('>>> InstanceInfo component tests', () => { it('Service with version v1', () => { - selectedService.apiId = { - v1: 'zowe.apiml.gateway', + selectedService.apis = { + v1: { apiId: 'zowe.apiml.gateway' }, }; const selectService = jest.fn(); const instanceInfo = shallow( @@ -35,8 +35,8 @@ describe('>>> InstanceInfo component tests', () => { }); it('No selected version, use defaultApiVersion', () => { - selectedService.apiId = { - v1: 'zowe.apiml.gateway', + selectedService.apis = { + v1: { apiId: 'zowe.apiml.gateway' }, }; selectedService.defaultApiVersion = ['v1']; const selectService = jest.fn(); @@ -46,8 +46,8 @@ describe('>>> InstanceInfo component tests', () => { }); it('No selected version and not set defaultApiVersion use key default', () => { - selectedService.apiId = { - default: 'zowe.apiml.gateway', + selectedService.apis = { + default: { apiId: 'zowe.apiml.gateway' }, }; selectedService.defaultApiVersion = null; const selectService = jest.fn(); diff --git a/api-catalog-ui/frontend/src/components/Swagger/SwaggerUI.test.jsx b/api-catalog-ui/frontend/src/components/Swagger/SwaggerUI.test.jsx index 55c754ddfc..d56c5067af 100644 --- a/api-catalog-ui/frontend/src/components/Swagger/SwaggerUI.test.jsx +++ b/api-catalog-ui/frontend/src/components/Swagger/SwaggerUI.test.jsx @@ -70,8 +70,8 @@ describe('>>> Swagger component tests', () => { openapi: '3.0.0', servers: [{ url: `https://bad.com${endpoint}` }], }), - apiId: { - default: 'enabler', + apis: { + default: { apiId: 'enabler' }, }, }; @@ -97,8 +97,8 @@ describe('>>> Swagger component tests', () => { openapi: '3.0.0', servers: [{ url: `https://bad.com${endpoint1}` }], }), - apiId: { - default: 'oldenabler', + apis: { + default: { apiId: 'oldenabler' }, }, }; @@ -114,8 +114,8 @@ describe('>>> Swagger component tests', () => { openapi: '3.0.0', servers: [{ url: `https://bad.com${endpoint2}` }], }), - apiId: { - default: 'oldenabler', + apis: { + default: { apiId: 'oldenabler' }, }, }; From e2e902f1ce904fd25068a827709a1a5c4f27d8eb Mon Sep 17 00:00:00 2001 From: Andrea Tabone Date: Mon, 18 Jul 2022 15:51:53 +0200 Subject: [PATCH 03/18] WIP - work on mapping code snippets to ApiInfo Signed-off-by: at670475 --- .../src/main/resources/application.yml | 7 ++++ .../java/org/zowe/apiml/config/ApiInfo.java | 5 ++- .../constants/EurekaMetadataDefinition.java | 5 +++ .../client/util/EurekaMetadataParser.java | 38 ++++++++++++++++++- .../src/main/resources/core-log-messages.yml | 7 ++++ .../gateway/services/ServicesInfoService.java | 1 + 6 files changed, 60 insertions(+), 3 deletions(-) diff --git a/api-catalog-services/src/main/resources/application.yml b/api-catalog-services/src/main/resources/application.yml index d659ebb1e6..bc3232ba1c 100644 --- a/api-catalog-services/src/main/resources/application.yml +++ b/api-catalog-services/src/main/resources/application.yml @@ -144,6 +144,13 @@ eureka: version: 1.0.0 gatewayUrl: api/v1 swaggerUrl: https://${apiml.service.hostname}:${apiml.service.port}${apiml.service.contextPath}/v3/api-docs + codeSnippet: + - endpoint: "/greetings" + codeBlock: | + ``` + System. out. println("Hello, World!"); + ``` + language: java service: title: API Catalog diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index b24075c0fb..7b64a6dc8b 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -22,6 +22,7 @@ import lombok.experimental.SuperBuilder; import java.io.IOException; +import java.util.List; /** * Represents one API provided by a service @@ -32,12 +33,13 @@ @SuperBuilder public class ApiInfo { - public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUrl, String documentationUrl) { + public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUrl, String documentationUrl, List codeSnippet) { this.apiId = apiId; this.gatewayUrl = gatewayUrl; this.version = version; this.swaggerUrl = swaggerUrl; this.documentationUrl = documentationUrl; + this.codeSnippet = codeSnippet; } /** @@ -61,6 +63,7 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr private String version; private String swaggerUrl; private String documentationUrl; + private List codeSnippet; @JsonDeserialize(using = StringToBooleanDeserializer.class) @Builder.Default diff --git a/common-service-core/src/main/java/org/zowe/apiml/constants/EurekaMetadataDefinition.java b/common-service-core/src/main/java/org/zowe/apiml/constants/EurekaMetadataDefinition.java index ff0075aaed..1954efbe8a 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/constants/EurekaMetadataDefinition.java +++ b/common-service-core/src/main/java/org/zowe/apiml/constants/EurekaMetadataDefinition.java @@ -39,6 +39,11 @@ private EurekaMetadataDefinition() { public static final String API_INFO_DOCUMENTATION_URL = "documentationUrl"; public static final String API_INFO_IS_DEFAULT = "defaultApi"; + public static final String CODE_SNIPPET = "codeSnippet"; + public static final String CODE_SNIPPET_ENDPOINT = "endpoint"; + public static final String CODE_SNIPPET_CODE_BLOCK = "codeBlock"; + public static final String CODE_SNIPPET_LANGUAGE = "language"; + public static final String AUTHENTICATION_SCHEME = "apiml.authentication.scheme"; public static final String AUTHENTICATION_APPLID = "apiml.authentication.applid"; public static final String AUTHENTICATION_SSO = "apiml.authentication.sso"; diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index ed1dc9b0b0..8b22f764c5 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -13,6 +13,7 @@ import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.auth.AuthenticationSchemes; import org.zowe.apiml.config.ApiInfo; +import org.zowe.apiml.config.CodeSnippet; import org.zowe.apiml.exception.MetadataValidationException; import org.zowe.apiml.message.log.ApimlLogger; import org.zowe.apiml.message.yaml.YamlMessageServiceInstance; @@ -23,6 +24,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -44,13 +46,14 @@ public class EurekaMetadataParser { public List parseApiInfo(Map eurekaMetadata) { Map apiInfo = new HashMap<>(); - + AtomicReference> codeSnippetMap = new AtomicReference<>(); + List codeSnippetList = new ArrayList<>(); eurekaMetadata.entrySet() .stream() .filter(metadata -> metadata.getKey().startsWith(API_INFO)) .forEach(metadata -> { String[] keys = metadata.getKey().split("\\."); - if (keys.length == 4) { + if (keys.length == 4 || keys.length == 6) { apiInfo.putIfAbsent(keys[2], new ApiInfo()); ApiInfo api = apiInfo.get(keys[2]); switch (keys[3]) { @@ -69,19 +72,50 @@ public List parseApiInfo(Map eurekaMetadata) { case API_INFO_DOCUMENTATION_URL: api.setDocumentationUrl(metadata.getValue()); break; + case CODE_SNIPPET: + codeSnippetMap.set(parseCodeSnippet(codeSnippetMap.get(), metadata, keys[4])); + break; case API_INFO_IS_DEFAULT: api.setDefaultApi(Boolean.parseBoolean(metadata.getValue())); break; + default: apimlLog.log("org.zowe.apiml.common.apiInfoParsingError", metadata); break; } + + // TODO we need to populate the ApiInfo with the list of codeSnippets which has been read from metadata. Currently this code is not working as the Map is getting refreshed + for (Map.Entry entry : codeSnippetMap.get().entrySet()) { + if (!codeSnippetList.contains(entry.getValue())) { + codeSnippetList.add(entry.getValue()); + } + } + api.setCodeSnippet(codeSnippetList); } }); return new ArrayList<>(apiInfo.values()); } + /** + * Parse eureka metadata and construct CodeSnippet with the values found + * + * @param eurekaMetadata the eureka metadata + * @return CodeSnippet list + */ + public Map parseCodeSnippet(Map codeSnippetMap, Map.Entry eurekaMetadata, String key) { + CodeSnippet codeSnippet = new CodeSnippet(); + if (eurekaMetadata.getKey().contains(CODE_SNIPPET_CODE_BLOCK)) { + codeSnippet.setCodeBlock(eurekaMetadata.getValue()); + } else if (eurekaMetadata.getKey().contains(CODE_SNIPPET_ENDPOINT)) { + codeSnippet.setEndpoint(eurekaMetadata.getValue()); + } else if (eurekaMetadata.getKey().contains(CODE_SNIPPET_LANGUAGE)) { + codeSnippet.setLanguage(eurekaMetadata.getValue()); + } + codeSnippetMap.put(key, codeSnippet); + return codeSnippetMap; + } + /** * Parse eureka metadata and add the routes found to the RoutedServices * diff --git a/common-service-core/src/main/resources/core-log-messages.yml b/common-service-core/src/main/resources/core-log-messages.yml index e9ef7c477c..f8fbdd9ab5 100644 --- a/common-service-core/src/main/resources/core-log-messages.yml +++ b/common-service-core/src/main/resources/core-log-messages.yml @@ -176,6 +176,13 @@ messages: reason: "An invalid apiInfo parameter was found while parsing the service metadata." action: "Remove or fix the referenced metadata parameter." + - key: org.zowe.apiml.common.codeSnippetParsingError + number: ZWEAM601 + type: WARNING + text: "Invalid parameter in metadata: '%s'" + reason: "An invalid codeSnippet parameter was found while parsing the service metadata." + action: "Remove or fix the referenced metadata parameter." + # Service specific messages # 700-999 diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java index 4bf6cd70c8..f20ab18164 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java @@ -130,6 +130,7 @@ private List getApiInfos(List appInst )) .documentationUrl(apiInfo.getDocumentationUrl()) .version(apiInfo.getVersion()) + .codeSnippet(apiInfo.getCodeSnippet()) .isDefaultApi(apiInfo.isDefaultApi()) .build()) .collect(Collectors.toList())); From a56a47e9285dc6ae29f3d8fb88a9acd0264e0b43 Mon Sep 17 00:00:00 2001 From: Andrea Tabone Date: Mon, 18 Jul 2022 15:54:35 +0200 Subject: [PATCH 04/18] Add missing class Signed-off-by: at670475 --- .../org/zowe/apiml/config/CodeSnippet.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 common-service-core/src/main/java/org/zowe/apiml/config/CodeSnippet.java diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/CodeSnippet.java b/common-service-core/src/main/java/org/zowe/apiml/config/CodeSnippet.java new file mode 100644 index 0000000000..8da61e9eae --- /dev/null +++ b/common-service-core/src/main/java/org/zowe/apiml/config/CodeSnippet.java @@ -0,0 +1,35 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ +package org.zowe.apiml.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Represents one code snippet provided by a service + */ +@NoArgsConstructor +@Data +@SuperBuilder +public class CodeSnippet { + + @JsonProperty(required = true) + private String endpoint; + private String codeBlock; + private String language; + + public CodeSnippet(String endpoint, String codeBlock, String language) { + this.endpoint = endpoint; + this.codeBlock = codeBlock; + this.language = language; + } +} From ad42536d87821061c4b5f7571bab49e7601a1e1a Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Mon, 18 Jul 2022 16:17:44 -0400 Subject: [PATCH 05/18] Use object mapper to parse apiinfo Signed-off-by: Carson Cook --- .../java/org/zowe/apiml/config/ApiInfo.java | 5 +- .../client/util/EurekaMetadataParser.java | 94 +++++-------------- .../client/util/EurekaMetadataParserTest.java | 71 ++++++++------ 3 files changed, 68 insertions(+), 102 deletions(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index 7b64a6dc8b..8ef4fc9ba9 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -22,6 +22,7 @@ import lombok.experimental.SuperBuilder; import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** @@ -33,13 +34,13 @@ @SuperBuilder public class ApiInfo { - public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUrl, String documentationUrl, List codeSnippet) { + public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUrl, String documentationUrl) { this.apiId = apiId; this.gatewayUrl = gatewayUrl; this.version = version; this.swaggerUrl = swaggerUrl; this.documentationUrl = documentationUrl; - this.codeSnippet = codeSnippet; + this.codeSnippet = new ArrayList<>(); } /** diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index 8b22f764c5..84ce315e39 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -9,11 +9,11 @@ */ package org.zowe.apiml.eurekaservice.client.util; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.BooleanUtils; import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.auth.AuthenticationSchemes; import org.zowe.apiml.config.ApiInfo; -import org.zowe.apiml.config.CodeSnippet; import org.zowe.apiml.exception.MetadataValidationException; import org.zowe.apiml.message.log.ApimlLogger; import org.zowe.apiml.message.yaml.YamlMessageServiceInstance; @@ -24,16 +24,15 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Collectors; import static org.zowe.apiml.constants.EurekaMetadataDefinition.*; public class EurekaMetadataParser { - private static final String THREE_STRING_MERGE_FORMAT = "%s.%s.%s"; + private final ObjectMapper objectMapper = new ObjectMapper(); private final ApimlLogger apimlLog = ApimlLogger.of(EurekaMetadataParser.class, YamlMessageServiceInstance.getInstance()); private final AuthenticationSchemes schemes = new AuthenticationSchemes(); @@ -43,77 +42,31 @@ public class EurekaMetadataParser { * @param eurekaMetadata the eureka metadata * @return ApiInfo list */ - public List parseApiInfo(Map eurekaMetadata) { - Map apiInfo = new HashMap<>(); - AtomicReference> codeSnippetMap = new AtomicReference<>(); - List codeSnippetList = new ArrayList<>(); + Map> collectedApiInfoEntries = new HashMap<>(); eurekaMetadata.entrySet() .stream() .filter(metadata -> metadata.getKey().startsWith(API_INFO)) .forEach(metadata -> { String[] keys = metadata.getKey().split("\\."); - if (keys.length == 4 || keys.length == 6) { - apiInfo.putIfAbsent(keys[2], new ApiInfo()); - ApiInfo api = apiInfo.get(keys[2]); - switch (keys[3]) { - case API_INFO_API_ID: - api.setApiId(metadata.getValue()); - break; - case API_INFO_GATEWAY_URL: - api.setGatewayUrl(metadata.getValue()); - break; - case API_INFO_VERSION: - api.setVersion(metadata.getValue()); - break; - case API_INFO_SWAGGER_URL: - api.setSwaggerUrl(metadata.getValue()); - break; - case API_INFO_DOCUMENTATION_URL: - api.setDocumentationUrl(metadata.getValue()); - break; - case CODE_SNIPPET: - codeSnippetMap.set(parseCodeSnippet(codeSnippetMap.get(), metadata, keys[4])); - break; - case API_INFO_IS_DEFAULT: - api.setDefaultApi(Boolean.parseBoolean(metadata.getValue())); - break; - - default: - apimlLog.log("org.zowe.apiml.common.apiInfoParsingError", metadata); - break; - } - - // TODO we need to populate the ApiInfo with the list of codeSnippets which has been read from metadata. Currently this code is not working as the Map is getting refreshed - for (Map.Entry entry : codeSnippetMap.get().entrySet()) { - if (!codeSnippetList.contains(entry.getValue())) { - codeSnippetList.add(entry.getValue()); - } - } - api.setCodeSnippet(codeSnippetList); + if (keys.length == 4) { + collectedApiInfoEntries.putIfAbsent(keys[2], new HashMap<>()); + Map apiInfoEntries = collectedApiInfoEntries.get(keys[2]); + + apiInfoEntries.put(keys[3], metadata.getValue()); + collectedApiInfoEntries.put(keys[2], apiInfoEntries); } }); - return new ArrayList<>(apiInfo.values()); - } - - /** - * Parse eureka metadata and construct CodeSnippet with the values found - * - * @param eurekaMetadata the eureka metadata - * @return CodeSnippet list - */ - public Map parseCodeSnippet(Map codeSnippetMap, Map.Entry eurekaMetadata, String key) { - CodeSnippet codeSnippet = new CodeSnippet(); - if (eurekaMetadata.getKey().contains(CODE_SNIPPET_CODE_BLOCK)) { - codeSnippet.setCodeBlock(eurekaMetadata.getValue()); - } else if (eurekaMetadata.getKey().contains(CODE_SNIPPET_ENDPOINT)) { - codeSnippet.setEndpoint(eurekaMetadata.getValue()); - } else if (eurekaMetadata.getKey().contains(CODE_SNIPPET_LANGUAGE)) { - codeSnippet.setLanguage(eurekaMetadata.getValue()); - } - codeSnippetMap.put(key, codeSnippet); - return codeSnippetMap; + List apiInfoList = new ArrayList<>(); + collectedApiInfoEntries.values().forEach(fields -> { + try { + apiInfoList.add(objectMapper.convertValue(fields, ApiInfo.class)); + } catch (Exception e) { + apimlLog.log("org.zowe.apiml.common.apiInfoParsingError", fields); + } + }); + return apiInfoList; } /** @@ -129,7 +82,6 @@ public RoutedServices parseRoutes(Map eurekaMetadata) { return routes; } - /** * Parse eureka metadata and return list of routes * @@ -253,11 +205,11 @@ private static void validateUrl(String url, Supplier exceptionSupplier) public Authentication parseAuthentication(Map eurekaMetadata) { return Authentication.builder() - .applid(eurekaMetadata.get(AUTHENTICATION_APPLID)) - .scheme(schemes.map(eurekaMetadata.get(AUTHENTICATION_SCHEME))) - .headers(eurekaMetadata.get(AUTHENTICATION_HEADERS)) - .supportsSso(BooleanUtils.toBooleanObject(eurekaMetadata.get(AUTHENTICATION_SSO))) - .build(); + .applid(eurekaMetadata.get(AUTHENTICATION_APPLID)) + .scheme(schemes.map(eurekaMetadata.get(AUTHENTICATION_SCHEME))) + .headers(eurekaMetadata.get(AUTHENTICATION_HEADERS)) + .supportsSso(BooleanUtils.toBooleanObject(eurekaMetadata.get(AUTHENTICATION_SSO))) + .build(); } } diff --git a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java index 799a293b42..f54da7ebb0 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java @@ -9,6 +9,7 @@ */ package org.zowe.apiml.eurekaservice.client.util; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.config.ApiInfo; @@ -28,29 +29,41 @@ class EurekaMetadataParserTest { private final EurekaMetadataParser eurekaMetadataParser = new EurekaMetadataParser(); - @Test - void testParseApiInfo() { - Map metadata = new HashMap<>(); - metadata.put(API_INFO + ".1." + API_INFO_GATEWAY_URL, "gatewayUrl"); - metadata.put(API_INFO + ".2." + API_INFO_GATEWAY_URL, "gatewayUrl2"); - metadata.put(API_INFO + ".2." + API_INFO_SWAGGER_URL, "swagger"); - metadata.put(API_INFO + ".2." + API_INFO_DOCUMENTATION_URL, "doc"); - metadata.put(API_INFO + ".1." + API_INFO_API_ID, "zowe.apiml.test"); - metadata.put(API_INFO + ".1." + API_INFO_VERSION, "1.0.0"); - metadata.put(API_INFO + ".1." + API_INFO_IS_DEFAULT, "true"); - metadata.put(API_INFO + ".1.badArgument", "garbage"); - - List info = eurekaMetadataParser.parseApiInfo(metadata); - - assertEquals(2, info.size()); - assertEquals("gatewayUrl", info.get(0).getGatewayUrl()); - assertEquals("zowe.apiml.test", info.get(0).getApiId()); - assertEquals("1.0.0", info.get(0).getVersion()); - assertTrue(info.get(0).isDefaultApi()); - assertEquals("gatewayUrl2", info.get(1).getGatewayUrl()); - assertEquals("swagger", info.get(1).getSwaggerUrl()); - assertEquals("doc", info.get(1).getDocumentationUrl()); - assertFalse(info.get(1).isDefaultApi()); + @Nested + class WhenParseApiInfo { + @Test + void givenTwoEntries_thenReturnTwoInstances() { + Map metadata = new HashMap<>(); + metadata.put(API_INFO + ".1." + API_INFO_GATEWAY_URL, "gatewayUrl"); + metadata.put(API_INFO + ".2." + API_INFO_GATEWAY_URL, "gatewayUrl2"); + metadata.put(API_INFO + ".2." + API_INFO_SWAGGER_URL, "swagger"); + metadata.put(API_INFO + ".2." + API_INFO_DOCUMENTATION_URL, "doc"); + metadata.put(API_INFO + ".1." + API_INFO_API_ID, "zowe.apiml.test"); + metadata.put(API_INFO + ".1." + API_INFO_VERSION, "1.0.0"); + metadata.put(API_INFO + ".1." + API_INFO_IS_DEFAULT, "true"); + + List info = eurekaMetadataParser.parseApiInfo(metadata); + + assertEquals(2, info.size()); + assertEquals("gatewayUrl", info.get(0).getGatewayUrl()); + assertEquals("zowe.apiml.test", info.get(0).getApiId()); + assertEquals("1.0.0", info.get(0).getVersion()); + assertTrue(info.get(0).isDefaultApi()); + assertEquals("gatewayUrl2", info.get(1).getGatewayUrl()); + assertEquals("swagger", info.get(1).getSwaggerUrl()); + assertEquals("doc", info.get(1).getDocumentationUrl()); + assertFalse(info.get(1).isDefaultApi()); + } + + @Test + void givenBadField_thenDontReturnInstance() { + Map metadata = new HashMap<>(); + metadata.put(API_INFO + ".1." + API_INFO_API_ID, "zowe.apiml.test"); + metadata.put(API_INFO + ".2." + "badargument", "value"); + + List info = eurekaMetadataParser.parseApiInfo(metadata); + assertEquals(1, info.size()); + } } @Test @@ -71,11 +84,11 @@ void testParseRoutes() { RoutedServices expectedRoutes = new RoutedServices(); expectedRoutes.addRoutedService( - new RoutedService("api-v1", "api/v1", "/")); + new RoutedService("api-v1", "api/v1", "/")); expectedRoutes.addRoutedService( - new RoutedService("api-v2", "api/v2", "/test")); + new RoutedService("api-v2", "api/v2", "/test")); expectedRoutes.addRoutedService( - new RoutedService("api-v5", "api/v5", "/test")); + new RoutedService("api-v5", "api/v5", "/test")); assertEquals(expectedRoutes.toString(), routes.toString()); } @@ -96,9 +109,9 @@ void testParseToListRoute() { List actualRoutes = eurekaMetadataParser.parseToListRoute(metadata); List expectedListRoute = Arrays.asList( - new RoutedService("api-v1", "api/v1", "/"), - new RoutedService("api-v2", "api/v2", "/test"), - new RoutedService("api-v5", "api/v5", "/test") + new RoutedService("api-v1", "api/v1", "/"), + new RoutedService("api-v2", "api/v2", "/test"), + new RoutedService("api-v5", "api/v5", "/test") ); assertEquals(3, actualRoutes.size(), "List route size is different"); From 6c4d3e20e33a2e888173c3e8a71296c90ac17a62 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Mon, 18 Jul 2022 16:53:57 -0400 Subject: [PATCH 06/18] Deserialize one code block Signed-off-by: Carson Cook --- .../java/org/zowe/apiml/config/ApiInfo.java | 4 ++-- .../client/util/EurekaMetadataParser.java | 23 +++++++++++++------ .../client/util/EurekaMetadataParserTest.java | 3 +++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index 8ef4fc9ba9..6ca77b29bc 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -40,7 +40,6 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr this.version = version; this.swaggerUrl = swaggerUrl; this.documentationUrl = documentationUrl; - this.codeSnippet = new ArrayList<>(); } /** @@ -64,7 +63,8 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr private String version; private String swaggerUrl; private String documentationUrl; - private List codeSnippet; + + private CodeSnippet codeSnippet; @JsonDeserialize(using = StringToBooleanDeserializer.class) @Builder.Default diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index 84ce315e39..f187f8d317 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -43,18 +43,27 @@ public class EurekaMetadataParser { * @return ApiInfo list */ public List parseApiInfo(Map eurekaMetadata) { - Map> collectedApiInfoEntries = new HashMap<>(); + Map> collectedApiInfoEntries = new HashMap<>(); eurekaMetadata.entrySet() .stream() .filter(metadata -> metadata.getKey().startsWith(API_INFO)) .forEach(metadata -> { String[] keys = metadata.getKey().split("\\."); - if (keys.length == 4) { - collectedApiInfoEntries.putIfAbsent(keys[2], new HashMap<>()); - Map apiInfoEntries = collectedApiInfoEntries.get(keys[2]); - - apiInfoEntries.put(keys[3], metadata.getValue()); - collectedApiInfoEntries.put(keys[2], apiInfoEntries); + if (keys.length >= 4) { // at least 4 keys split by '.' if is an ApiInfo config entry + String entryIndex = keys[2]; + collectedApiInfoEntries.putIfAbsent(entryIndex, new HashMap<>()); + Map apiInfoEntries = collectedApiInfoEntries.get(entryIndex); + + if (metadata.getKey().contains(CODE_SNIPPET)) { + apiInfoEntries.putIfAbsent(keys[3], new HashMap<>()); + Map codeSnippetMap = (Map) apiInfoEntries.get(keys[3]); + + codeSnippetMap.put(keys[4], metadata.getValue()); + apiInfoEntries.put(keys[3], codeSnippetMap); + } else { + apiInfoEntries.put(keys[3], metadata.getValue()); + } + collectedApiInfoEntries.put(entryIndex, apiInfoEntries); } }); diff --git a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java index f54da7ebb0..0b2038ed30 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java @@ -34,6 +34,9 @@ class WhenParseApiInfo { @Test void givenTwoEntries_thenReturnTwoInstances() { Map metadata = new HashMap<>(); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_ENDPOINT, "endpoint"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_LANGUAGE, "java"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_CODE_BLOCK, "codeblock"); metadata.put(API_INFO + ".1." + API_INFO_GATEWAY_URL, "gatewayUrl"); metadata.put(API_INFO + ".2." + API_INFO_GATEWAY_URL, "gatewayUrl2"); metadata.put(API_INFO + ".2." + API_INFO_SWAGGER_URL, "swagger"); From 24072160b98b8fee83681aa46bd3a92012e05e45 Mon Sep 17 00:00:00 2001 From: Andrea Tabone Date: Tue, 19 Jul 2022 13:10:10 +0200 Subject: [PATCH 07/18] Convert map to map of list object in order to avoid replacing of values in nested map Signed-off-by: at670475 --- .../client/util/EurekaMetadataParser.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index f187f8d317..a1901a984d 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -43,7 +43,7 @@ public class EurekaMetadataParser { * @return ApiInfo list */ public List parseApiInfo(Map eurekaMetadata) { - Map> collectedApiInfoEntries = new HashMap<>(); + Map>> collectedApiInfoEntries = new HashMap<>(); eurekaMetadata.entrySet() .stream() .filter(metadata -> metadata.getKey().startsWith(API_INFO)) @@ -52,22 +52,23 @@ public List parseApiInfo(Map eurekaMetadata) { if (keys.length >= 4) { // at least 4 keys split by '.' if is an ApiInfo config entry String entryIndex = keys[2]; collectedApiInfoEntries.putIfAbsent(entryIndex, new HashMap<>()); - Map apiInfoEntries = collectedApiInfoEntries.get(entryIndex); + Map> apiInfoEntries = collectedApiInfoEntries.get(entryIndex); if (metadata.getKey().contains(CODE_SNIPPET)) { - apiInfoEntries.putIfAbsent(keys[3], new HashMap<>()); - Map codeSnippetMap = (Map) apiInfoEntries.get(keys[3]); + apiInfoEntries.putIfAbsent(keys[3], new ArrayList<>()); + List codeSnippetList = apiInfoEntries.get(keys[3]); - codeSnippetMap.put(keys[4], metadata.getValue()); - apiInfoEntries.put(keys[3], codeSnippetMap); + codeSnippetList.add(metadata.getValue()); + apiInfoEntries.put(keys[3], codeSnippetList); } else { - apiInfoEntries.put(keys[3], metadata.getValue()); + apiInfoEntries.put(keys[3], Collections.singletonList(metadata.getValue())); } collectedApiInfoEntries.put(entryIndex, apiInfoEntries); } }); List apiInfoList = new ArrayList<>(); + // this returns undefined and doesnt populate the list collectedApiInfoEntries.values().forEach(fields -> { try { apiInfoList.add(objectMapper.convertValue(fields, ApiInfo.class)); From 79de3eca2a6da525c49dc5a4dbd03c27a79346ca Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 09:14:43 -0400 Subject: [PATCH 08/18] Support multiple codesnippet entries Signed-off-by: Carson Cook --- .../java/org/zowe/apiml/config/ApiInfo.java | 3 ++- .../client/util/EurekaMetadataParser.java | 27 +++++++++++++------ .../client/util/EurekaMetadataParserTest.java | 27 ++++++++++++++++--- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index 6ca77b29bc..908c3da4e8 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -64,7 +64,8 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr private String swaggerUrl; private String documentationUrl; - private CodeSnippet codeSnippet; + @Builder.Default + private List codeSnippet = new ArrayList<>(); @JsonDeserialize(using = StringToBooleanDeserializer.class) @Builder.Default diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index a1901a984d..e2601dcb4a 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -43,7 +43,7 @@ public class EurekaMetadataParser { * @return ApiInfo list */ public List parseApiInfo(Map eurekaMetadata) { - Map>> collectedApiInfoEntries = new HashMap<>(); + Map> collectedApiInfoEntries = new HashMap<>(); eurekaMetadata.entrySet() .stream() .filter(metadata -> metadata.getKey().startsWith(API_INFO)) @@ -51,26 +51,37 @@ public List parseApiInfo(Map eurekaMetadata) { String[] keys = metadata.getKey().split("\\."); if (keys.length >= 4) { // at least 4 keys split by '.' if is an ApiInfo config entry String entryIndex = keys[2]; + String entryKey = keys[3]; collectedApiInfoEntries.putIfAbsent(entryIndex, new HashMap<>()); - Map> apiInfoEntries = collectedApiInfoEntries.get(entryIndex); + Map apiInfoEntries = collectedApiInfoEntries.get(entryIndex); if (metadata.getKey().contains(CODE_SNIPPET)) { - apiInfoEntries.putIfAbsent(keys[3], new ArrayList<>()); - List codeSnippetList = apiInfoEntries.get(keys[3]); + String codeSnippetEntryIndex = keys[4]; + String codeSnippetChildKey = keys[5]; - codeSnippetList.add(metadata.getValue()); - apiInfoEntries.put(keys[3], codeSnippetList); + apiInfoEntries.putIfAbsent(entryKey, new HashMap<>()); + Map> codeSnippetMap = (Map>) apiInfoEntries.get(entryKey); + codeSnippetMap.putIfAbsent(codeSnippetEntryIndex, new HashMap<>()); + + Map codeSnippetChildEntry = codeSnippetMap.get(codeSnippetEntryIndex); + codeSnippetChildEntry.put(codeSnippetChildKey, metadata.getValue()); + codeSnippetMap.put(codeSnippetEntryIndex, codeSnippetChildEntry); + apiInfoEntries.put(entryKey, codeSnippetMap); } else { - apiInfoEntries.put(keys[3], Collections.singletonList(metadata.getValue())); + apiInfoEntries.put(entryKey, metadata.getValue()); } collectedApiInfoEntries.put(entryIndex, apiInfoEntries); } }); List apiInfoList = new ArrayList<>(); - // this returns undefined and doesnt populate the list collectedApiInfoEntries.values().forEach(fields -> { try { + if (fields.containsKey(CODE_SNIPPET)) { + Map> codeSnippetMap = (Map>) fields.get(CODE_SNIPPET); + List> codeSnippetList = new ArrayList<>(codeSnippetMap.values()); + fields.put(CODE_SNIPPET, codeSnippetList); + } apiInfoList.add(objectMapper.convertValue(fields, ApiInfo.class)); } catch (Exception e) { apimlLog.log("org.zowe.apiml.common.apiInfoParsingError", fields); diff --git a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java index 0b2038ed30..92720cf2b5 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.config.ApiInfo; +import org.zowe.apiml.config.CodeSnippet; import org.zowe.apiml.exception.MetadataValidationException; import org.zowe.apiml.product.routing.RoutedService; import org.zowe.apiml.product.routing.RoutedServices; @@ -34,9 +35,9 @@ class WhenParseApiInfo { @Test void givenTwoEntries_thenReturnTwoInstances() { Map metadata = new HashMap<>(); - metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_ENDPOINT, "endpoint"); - metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_LANGUAGE, "java"); - metadata.put(API_INFO + ".1." + CODE_SNIPPET + "." + CODE_SNIPPET_CODE_BLOCK, "codeblock"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_ENDPOINT, "endpoint"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_LANGUAGE, "java"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_CODE_BLOCK, "codeblock"); metadata.put(API_INFO + ".1." + API_INFO_GATEWAY_URL, "gatewayUrl"); metadata.put(API_INFO + ".2." + API_INFO_GATEWAY_URL, "gatewayUrl2"); metadata.put(API_INFO + ".2." + API_INFO_SWAGGER_URL, "swagger"); @@ -58,6 +59,26 @@ void givenTwoEntries_thenReturnTwoInstances() { assertFalse(info.get(1).isDefaultApi()); } + @Test + void givenCodeSnippets_thenReturnApiInfoWithCodeSnippets() { + Map metadata = new HashMap<>(); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_ENDPOINT, "endpoint1"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_CODE_BLOCK, "codeblock1"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".1." + CODE_SNIPPET_LANGUAGE, "language1"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_ENDPOINT, "endpoint2"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_CODE_BLOCK, "codeblock2"); + metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_LANGUAGE, "language2"); + + List info = eurekaMetadataParser.parseApiInfo(metadata); + + CodeSnippet expectedCodeSnippet1 = new CodeSnippet("endpoint1", "codeblock1", "language1"); + CodeSnippet expectedCodeSnippet2 = new CodeSnippet("endpoint2", "codeblock2", "language2"); + + assertEquals(1, info.size()); + assertEquals(expectedCodeSnippet1, info.get(0).getCodeSnippet().get(0)); + assertEquals(expectedCodeSnippet2, info.get(0).getCodeSnippet().get(1)); + } + @Test void givenBadField_thenDontReturnInstance() { Map metadata = new HashMap<>(); From 5aec1737d57104cfe6ef7d41818098deb8f224d3 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 09:42:35 -0400 Subject: [PATCH 09/18] Improve codesnippet testing Signed-off-by: Carson Cook --- .../apiml/eurekaservice/client/util/EurekaMetadataParser.java | 2 +- .../eurekaservice/client/util/EurekaMetadataParserTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index e2601dcb4a..1165a9aa82 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -55,7 +55,7 @@ public List parseApiInfo(Map eurekaMetadata) { collectedApiInfoEntries.putIfAbsent(entryIndex, new HashMap<>()); Map apiInfoEntries = collectedApiInfoEntries.get(entryIndex); - if (metadata.getKey().contains(CODE_SNIPPET)) { + if (metadata.getKey().contains(CODE_SNIPPET) && keys.length >= 6) { String codeSnippetEntryIndex = keys[4]; String codeSnippetChildKey = keys[5]; diff --git a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java index 92720cf2b5..e15575d96f 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java @@ -68,6 +68,7 @@ void givenCodeSnippets_thenReturnApiInfoWithCodeSnippets() { metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_ENDPOINT, "endpoint2"); metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_CODE_BLOCK, "codeblock2"); metadata.put(API_INFO + ".1." + CODE_SNIPPET + ".2." + CODE_SNIPPET_LANGUAGE, "language2"); + metadata.put(API_INFO + ".2." + CODE_SNIPPET, "badvalue"); List info = eurekaMetadataParser.parseApiInfo(metadata); From bcd44503e7508711b69d7d593eea6bafbc252f03 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 09:43:26 -0400 Subject: [PATCH 10/18] Suppress unchecked warnings Signed-off-by: Carson Cook --- .../eurekaservice/client/util/EurekaMetadataParser.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index 1165a9aa82..d75bde5432 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -60,6 +60,8 @@ public List parseApiInfo(Map eurekaMetadata) { String codeSnippetChildKey = keys[5]; apiInfoEntries.putIfAbsent(entryKey, new HashMap<>()); + + @SuppressWarnings("unchecked") Map> codeSnippetMap = (Map>) apiInfoEntries.get(entryKey); codeSnippetMap.putIfAbsent(codeSnippetEntryIndex, new HashMap<>()); @@ -78,8 +80,11 @@ public List parseApiInfo(Map eurekaMetadata) { collectedApiInfoEntries.values().forEach(fields -> { try { if (fields.containsKey(CODE_SNIPPET)) { + + @SuppressWarnings("unchecked") Map> codeSnippetMap = (Map>) fields.get(CODE_SNIPPET); List> codeSnippetList = new ArrayList<>(codeSnippetMap.values()); + fields.put(CODE_SNIPPET, codeSnippetList); } apiInfoList.add(objectMapper.convertValue(fields, ApiInfo.class)); From a6392b0e752f9003f77c789e05919a998d7de3af Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 13:41:02 -0400 Subject: [PATCH 11/18] Propagate codesnippet config to UI Signed-off-by: Carson Cook --- .../main/java/org/zowe/apiml/config/ApiInfo.java | 4 ++++ .../client/util/EurekaMetadataParser.java | 11 +++++++++++ config/local/api-defs/staticclient.yml | 7 +++++++ .../src/main/resources/application.yml | 13 +++++++++++++ .../staticdef/ServiceDefinitionProcessor.java | 4 ++-- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index 908c3da4e8..7d69409ba4 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -71,6 +71,10 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr @Builder.Default private boolean isDefaultApi = false; + public void addCodeSnippet(CodeSnippet newCodeSnippet) { + this.codeSnippet.add(newCodeSnippet); + } + @JsonIgnore public int getMajorVersion() { if (version == null) { diff --git a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java index d75bde5432..c36f452986 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java +++ b/common-service-core/src/main/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParser.java @@ -14,6 +14,7 @@ import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.auth.AuthenticationSchemes; import org.zowe.apiml.config.ApiInfo; +import org.zowe.apiml.config.CodeSnippet; import org.zowe.apiml.exception.MetadataValidationException; import org.zowe.apiml.message.log.ApimlLogger; import org.zowe.apiml.message.yaml.YamlMessageServiceInstance; @@ -31,6 +32,7 @@ public class EurekaMetadataParser { private static final String THREE_STRING_MERGE_FORMAT = "%s.%s.%s"; + private static final String FIVE_STRING_MERGE_FORMAT = "%s.%s.%s.%s.%s"; private final ObjectMapper objectMapper = new ObjectMapper(); private final ApimlLogger apimlLog = ApimlLogger.of(EurekaMetadataParser.class, YamlMessageServiceInstance.getInstance()); @@ -216,6 +218,15 @@ public static Map generateMetadata(String serviceId, ApiInfo api metadata.put(String.format(THREE_STRING_MERGE_FORMAT, API_INFO, encodedGatewayUrl, API_INFO_DOCUMENTATION_URL), apiInfo.getDocumentationUrl()); } + List codeSnippets = apiInfo.getCodeSnippet(); + if (codeSnippets != null && !codeSnippets.isEmpty()) { + for (int i = 0; i < codeSnippets.size(); i++) { + metadata.put(String.format(FIVE_STRING_MERGE_FORMAT, API_INFO, encodedGatewayUrl, CODE_SNIPPET, i, CODE_SNIPPET_ENDPOINT), codeSnippets.get(i).getEndpoint()); + metadata.put(String.format(FIVE_STRING_MERGE_FORMAT, API_INFO, encodedGatewayUrl, CODE_SNIPPET, i, CODE_SNIPPET_CODE_BLOCK), codeSnippets.get(i).getCodeBlock()); + metadata.put(String.format(FIVE_STRING_MERGE_FORMAT, API_INFO, encodedGatewayUrl, CODE_SNIPPET, i, CODE_SNIPPET_LANGUAGE), codeSnippets.get(i).getLanguage()); + } + } + metadata.put(String.format(THREE_STRING_MERGE_FORMAT, API_INFO, encodedGatewayUrl, API_INFO_IS_DEFAULT), String.valueOf(apiInfo.isDefaultApi())); return metadata; diff --git a/config/local/api-defs/staticclient.yml b/config/local/api-defs/staticclient.yml index 010211891d..21c73ac9e2 100644 --- a/config/local/api-defs/staticclient.yml +++ b/config/local/api-defs/staticclient.yml @@ -54,6 +54,13 @@ services: - apiId: zowe.apiml.discoverableclient gatewayUrl: api/v1 version: 1.0.0 + codeSnippet: + - endpoint: /pets + language: java + codeBlock: | + ``` + System.out.println("Pets code snippet"); + ``` - serviceId: zowejwt # unique lowercase ID of the service catalogUiTileId: static # ID of the API Catalog UI tile (visual grouping of the services) diff --git a/discoverable-client/src/main/resources/application.yml b/discoverable-client/src/main/resources/application.yml index 9e568dc2da..3ef7d55687 100644 --- a/discoverable-client/src/main/resources/application.yml +++ b/discoverable-client/src/main/resources/application.yml @@ -75,6 +75,19 @@ apiml: swaggerUrl: ${apiml.service.scheme}://${apiml.service.hostname}:${apiml.service.port}${apiml.service.contextPath}/v3/api-docs/apiv1 documentationUrl: https://www.zowe.org defaultApi: true + codeSnippet: + - endpoint: /greeting + language: java + codeBlock: | + ``` + System.out.println("Greeting code snippet"); + ``` + - endpoint: /{yourName}/greeting + language: java + codeBlock: | + ``` + System.out.println("Your name greeting code snippet"); + ``` - apiId: zowe.apiml.discoverableclient.ws version: 1.0.0 gatewayUrl: graphql/v1 diff --git a/discovery-service/src/main/java/org/zowe/apiml/discovery/staticdef/ServiceDefinitionProcessor.java b/discovery-service/src/main/java/org/zowe/apiml/discovery/staticdef/ServiceDefinitionProcessor.java index c294124c65..51db84b97f 100644 --- a/discovery-service/src/main/java/org/zowe/apiml/discovery/staticdef/ServiceDefinitionProcessor.java +++ b/discovery-service/src/main/java/org/zowe/apiml/discovery/staticdef/ServiceDefinitionProcessor.java @@ -351,7 +351,7 @@ private void setMetadataTile(Map metadata, CatalogUiTile tile) { metadata.put(CATALOG_DESCRIPTION, tile.getDescription()); } - private void setMetadataAppInfo(Map metadata, List appInfoList, String serviceId) { + private void setMetadataApiInfo(Map metadata, List appInfoList, String serviceId) { if (appInfoList == null) return; for (ApiInfo apiInfo : appInfoList) { @@ -392,7 +392,7 @@ private Map createMetadata(Service service, URL url, CatalogUiTi setMetadataRoutes(metadata, service.getRoutes(), url); setMetadataTile(metadata, tile); - setMetadataAppInfo(metadata, service.getApiInfo(), service.getServiceId()); + setMetadataApiInfo(metadata, service.getApiInfo(), service.getServiceId()); setMetadataAuthentication(metadata, service.getAuthentication()); setCustomMetadata(metadata, service.getCustomMetadata()); From 68ee4917b7de4f4a799d3acfe403993dbcb81635 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 13:59:04 -0400 Subject: [PATCH 12/18] Add unit test Signed-off-by: Carson Cook --- .../java/org/zowe/apiml/config/ApiInfo.java | 1 + .../client/util/EurekaMetadataParserTest.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java index 7d69409ba4..90f425c197 100644 --- a/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java +++ b/common-service-core/src/main/java/org/zowe/apiml/config/ApiInfo.java @@ -40,6 +40,7 @@ public ApiInfo(String apiId, String gatewayUrl, String version, String swaggerUr this.version = version; this.swaggerUrl = swaggerUrl; this.documentationUrl = documentationUrl; + this.codeSnippet = new ArrayList<>(); } /** diff --git a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java index e15575d96f..11ffdfd7be 100644 --- a/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java +++ b/common-service-core/src/test/java/org/zowe/apiml/eurekaservice/client/util/EurekaMetadataParserTest.java @@ -181,6 +181,38 @@ void generateFullMetadata() { assertEquals(documentationUrl, metaDocumentationUrl); } + @Test + void generateMetadataWithCodeSnippets() { + String endpoint1 = "/endpoint1"; + String codeBlock1 = "code1"; + String language1 = "java1"; + String endpoint2 = "/endpoint2"; + String codeBlock2 = "code2"; + String language2 = "java2"; + String metadataPrefix = API_INFO + ".api-v1."; + + ApiInfo apiInfo = new ApiInfo("zowe.apiml.test", "api/v1", "1.0.0", "https://service/api-doc", "https://www.zowe.org"); + apiInfo.addCodeSnippet(new CodeSnippet(endpoint1, codeBlock1, language1)); + apiInfo.addCodeSnippet(new CodeSnippet(endpoint2, codeBlock2, language2)); + Map metadata = EurekaMetadataParser.generateMetadata("test service", apiInfo); + + String codeSnippetEndpoint1 = metadata.get(metadataPrefix + CODE_SNIPPET + ".0." + CODE_SNIPPET_ENDPOINT); + String codeSnippetBlock1 = metadata.get(metadataPrefix + CODE_SNIPPET + ".0." + CODE_SNIPPET_CODE_BLOCK); + String codeSnippetLanguage1 = metadata.get(metadataPrefix + CODE_SNIPPET + ".0." + CODE_SNIPPET_LANGUAGE); + + assertEquals(codeSnippetEndpoint1, endpoint1); + assertEquals(codeSnippetBlock1, codeBlock1); + assertEquals(codeSnippetLanguage1, language1); + + String codeSnippetEndpoint2 = metadata.get(metadataPrefix + CODE_SNIPPET + ".1." + CODE_SNIPPET_ENDPOINT); + String codeSnippetBlock2 = metadata.get(metadataPrefix + CODE_SNIPPET + ".1." + CODE_SNIPPET_CODE_BLOCK); + String codeSnippetLanguage2 = metadata.get(metadataPrefix + CODE_SNIPPET + ".1." + CODE_SNIPPET_LANGUAGE); + + assertEquals(codeSnippetEndpoint2, endpoint2); + assertEquals(codeSnippetBlock2, codeBlock2); + assertEquals(codeSnippetLanguage2, language2); + } + @Test void generateMetadataWithNoGatewayUrl() { String serviceId = "test service"; From aedea1d19186b4f7b32c6d89afe8949e60d22fd3 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Tue, 19 Jul 2022 14:40:48 -0400 Subject: [PATCH 13/18] Add ApiInfo integration test Signed-off-by: Carson Cook --- ...alogDiscoverableClientIntegrationTest.java | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java index 0ec3d02700..35eab82444 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java @@ -11,6 +11,7 @@ import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; +import net.minidev.json.JSONArray; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; @@ -30,6 +31,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isEmptyString; import static org.junit.jupiter.api.Assertions.*; import static org.zowe.apiml.util.requests.Endpoints.*; @@ -90,10 +92,7 @@ void givenUrlForContainer() throws IOException { String containerJsonResponse = EntityUtils.toString(response.getEntity()); DocumentContext containerJsonContext = JsonPath.parse(containerJsonResponse); - // Validate container - assertEquals("cademoapps", containerJsonContext.read("$[0].id")); - assertEquals("Sample API Mediation Layer Applications", containerJsonContext.read("$[0].title")); - assertEquals("UP", containerJsonContext.read("$[0].status")); + validateContainer(containerJsonContext); // Get Discoverable Client swagger String dcJsonResponse = containerJsonContext.read("$[0].services[0]").toString(); @@ -104,6 +103,38 @@ void givenUrlForContainer() throws IOException { } } + @Test + void givenApis_whenGetContainer_thenApisReturned() throws IOException { + HttpResponse response = getResponse(DISCOVERABLE_CLIENT_CONTAINER_ENDPOINT, HttpStatus.SC_OK); + String containerJsonResponse = EntityUtils.toString(response.getEntity()); + DocumentContext containerJsonContext = JsonPath.parse(containerJsonResponse); + + validateContainer(containerJsonContext); + + LinkedHashMap> apis = containerJsonContext.read("$[0].services[0].apis"); + + assertNotNull(apis.get("default")); + assertNotNull(apis.get("zowe.apiml.discoverableclient.rest v2.0.0")); + assertNotNull(apis.get("zowe.apiml.discoverableclient.rest v1.0.0")); + assertNotNull(apis.get("zowe.apiml.discoverableclient.ws v1.0.0")); + + LinkedHashMap defaultApi = apis.get("default"); + assertEquals("zowe.apiml.discoverableclient.rest", defaultApi.get("apiId")); + assertEquals("api/v1", defaultApi.get("gatewayUrl")); + assertEquals("1.0.0", defaultApi.get("version")); + assertEquals("https://localhost:10012/discoverableclient/v3/api-docs/apiv1", defaultApi.get("swaggerUrl")); + assertEquals("https://www.zowe.org", defaultApi.get("documentationUrl")); + assertEquals(true, defaultApi.get("defaultApi")); + + JSONArray codeSnippets = (JSONArray) defaultApi.get("codeSnippet"); + assertEquals(2, codeSnippets.size()); + LinkedHashMap codeSnippet = (LinkedHashMap) codeSnippets.get(0); + assertEquals("/greeting", codeSnippet.get("endpoint")); + assertNotNull(codeSnippet.get("codeBlock")); + assertThat(codeSnippet.get("codeBlock"), not(isEmptyString())); + assertEquals("java", codeSnippet.get("language")); + } + @Nested class WhenGettingDifferenceBetweenVersions { @Nested @@ -152,6 +183,12 @@ private HttpResponse getResponse(String endpoint, int returnCode) throws IOExcep return response; } + private void validateContainer(DocumentContext containerJsonContext) { + assertEquals("cademoapps", containerJsonContext.read("$[0].id")); + assertEquals("Sample API Mediation Layer Applications", containerJsonContext.read("$[0].title")); + assertEquals("UP", containerJsonContext.read("$[0].status")); + } + private void validateDiscoverableClientApiV1(String jsonResponse, DocumentContext jsonContext) throws IOException { String apiCatalogSwagger = "\n**************************\n" + "Integration Test: Discoverable Client Swagger" + From efaa4d3a4145570a257f4c9eb322ba760a8e76dc Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Wed, 20 Jul 2022 07:41:12 -0400 Subject: [PATCH 14/18] Remove console.log Signed-off-by: Carson Cook --- .../frontend/src/components/ServiceTab/InstanceInfo.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx index 49f1ed16f5..74f253ef54 100644 --- a/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx +++ b/api-catalog-ui/frontend/src/components/ServiceTab/InstanceInfo.jsx @@ -15,8 +15,7 @@ import Shield from '../ErrorBoundary/Shield/Shield'; export default class InstanceInfo extends Component { render() { const { selectedService, selectedVersion } = this.props; - console.log(selectedVersion); // eslint-disable-line no-console - console.log(selectedService); // eslint-disable-line no-console + const apiInfo = selectedService.apis[selectedVersion || selectedService.defaultApiVersion] || selectedService.apis.default; const { apiId } = apiInfo; From 583fefc2f17fc1eb1ede38c36626ba36bee0416d Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Wed, 20 Jul 2022 07:57:16 -0400 Subject: [PATCH 15/18] Fix integration test Signed-off-by: Carson Cook --- .../discovery/ApiCatalogDiscoverableClientIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java index 35eab82444..14003ba599 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/discovery/ApiCatalogDiscoverableClientIntegrationTest.java @@ -122,7 +122,7 @@ void givenApis_whenGetContainer_thenApisReturned() throws IOException { assertEquals("zowe.apiml.discoverableclient.rest", defaultApi.get("apiId")); assertEquals("api/v1", defaultApi.get("gatewayUrl")); assertEquals("1.0.0", defaultApi.get("version")); - assertEquals("https://localhost:10012/discoverableclient/v3/api-docs/apiv1", defaultApi.get("swaggerUrl")); + assertThat((String) defaultApi.get("swaggerUrl"), endsWith("/discoverableclient/v3/api-docs/apiv1")); assertEquals("https://www.zowe.org", defaultApi.get("documentationUrl")); assertEquals(true, defaultApi.get("defaultApi")); From a1f345f2a9796d2cc1358e0b2a8ebd16773d52c7 Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Wed, 20 Jul 2022 08:37:50 -0400 Subject: [PATCH 16/18] Fix code smells Signed-off-by: Carson Cook --- .../java/org/zowe/apiml/apicatalog/model/APIService.java | 3 ++- .../services/cached/CachedProductFamilyService.java | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java index 4a5e868c28..e957496172 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java @@ -16,6 +16,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -65,7 +66,7 @@ public class APIService implements Serializable { private boolean ssoAllInstances; @Schema(description = "The API information for each API ID for this service") - private Map apis; + private Map apis = new HashMap<>(); private List instances = new ArrayList<>(); diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java index 0b4873d986..040029eef8 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/services/cached/CachedProductFamilyService.java @@ -42,6 +42,8 @@ @Service public class CachedProductFamilyService { + private static final String DEFAULT_APIINFO_KEY = "default"; + @InjectApimlLogger private final ApimlLogger apimlLog = ApimlLogger.empty(); @@ -337,13 +339,13 @@ private APIService createAPIServiceFromInstance(InstanceInfo instanceInfo) { try { List apiInfoList = metadataParser.parseApiInfo(instanceInfo.getMetadata()); apiInfoList.stream().filter(apiInfo -> apiInfo.getApiId() != null).forEach(apiInfo -> { - String id = (apiInfo.getMajorVersion() < 0) ? "default" : apiInfo.getApiId() + " v" + apiInfo.getVersion(); + String id = (apiInfo.getMajorVersion() < 0) ? DEFAULT_APIINFO_KEY : apiInfo.getApiId() + " v" + apiInfo.getVersion(); apiInfoById.put(id, apiInfo); }); - if (!apiInfoById.containsKey("default")) { + if (!apiInfoById.containsKey(DEFAULT_APIINFO_KEY)) { ApiInfo defaultApiInfo = apiInfoList.stream().filter(ApiInfo::isDefaultApi).findFirst().orElse(null); - apiInfoById.put("default", defaultApiInfo); + apiInfoById.put(DEFAULT_APIINFO_KEY, defaultApiInfo); } } catch (Exception ex) { log.info("createApiServiceFromInstance#incorrectVersions {}", ex.getMessage()); From cd47754b7cca89c14cc9476a3c0917d5912569ef Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Wed, 20 Jul 2022 09:07:23 -0400 Subject: [PATCH 17/18] Fix code smell Signed-off-by: Carson Cook --- .../main/java/org/zowe/apiml/apicatalog/model/APIService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java index e957496172..86d7179032 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/model/APIService.java @@ -66,7 +66,7 @@ public class APIService implements Serializable { private boolean ssoAllInstances; @Schema(description = "The API information for each API ID for this service") - private Map apis = new HashMap<>(); + private Map apis = new HashMap<>(); // NOSONAR private List instances = new ArrayList<>(); From c1aa9b0cd953cc6bddb78e3730435c6388a40f6c Mon Sep 17 00:00:00 2001 From: Carson Cook Date: Fri, 22 Jul 2022 08:35:34 -0400 Subject: [PATCH 18/18] Remove unused message Signed-off-by: Carson Cook --- .../src/main/resources/core-log-messages.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/common-service-core/src/main/resources/core-log-messages.yml b/common-service-core/src/main/resources/core-log-messages.yml index f8fbdd9ab5..e9ef7c477c 100644 --- a/common-service-core/src/main/resources/core-log-messages.yml +++ b/common-service-core/src/main/resources/core-log-messages.yml @@ -176,13 +176,6 @@ messages: reason: "An invalid apiInfo parameter was found while parsing the service metadata." action: "Remove or fix the referenced metadata parameter." - - key: org.zowe.apiml.common.codeSnippetParsingError - number: ZWEAM601 - type: WARNING - text: "Invalid parameter in metadata: '%s'" - reason: "An invalid codeSnippet parameter was found while parsing the service metadata." - action: "Remove or fix the referenced metadata parameter." - # Service specific messages # 700-999