From 02ca838405ffd30efca975a7c23bd7912b1bbe6d Mon Sep 17 00:00:00 2001 From: janidu-wso2 Date: Fri, 13 Feb 2026 09:26:20 +0530 Subject: [PATCH 1/2] Implement Kong API builders and utility functions for REST and WebSocket APIs --- .../org/wso2/kong/client/KongConstants.java | 12 + .../client/KongFederatedAPIDiscovery.java | 276 +++++++----------- .../kong/client/builder/KongAPIBuilder.java | 96 ++++++ .../client/builder/KongAPIBuilderFactory.java | 49 ++++ .../kong/client/builder/KongApiBundle.java | 104 +++++++ .../client/builder/KongRestAPIBuilder.java | 125 ++++++++ .../builder/KongWebSocketAPIBuilder.java | 108 +++++++ .../wso2/kong/client/util/KongAPIUtil.java | 81 +++++ 8 files changed, 677 insertions(+), 174 deletions(-) create mode 100644 kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java create mode 100644 kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java create mode 100644 kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongApiBundle.java create mode 100644 kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongRestAPIBuilder.java create mode 100644 kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongWebSocketAPIBuilder.java diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongConstants.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongConstants.java index b1fa5f1..2fc1db3 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongConstants.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongConstants.java @@ -36,6 +36,18 @@ public class KongConstants { public static final String KONG_RATELIMIT_ADVANCED_PLUGIN_TYPE = "rate-limiting-advanced"; public static final String KONG_RATELIMIT_PLUGIN_TYPE = "rate-limiting"; + // API Types + public static final String API_TYPE_HTTP = "HTTP"; + public static final String API_TYPE_WS = "WS"; + + // Protocols + public static final String PROTOCOL_WS = "ws"; + public static final String PROTOCOL_WSS = "wss"; + + // Transports + public static final String TRANSPORT_HTTP = "http,https"; + public static final String TRANSPORT_WS = "ws,wss"; + // Commonly used default values and headers public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String BEARER_PREFIX = "Bearer "; diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java index 1052725..6a1ac0f 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java @@ -35,21 +35,20 @@ import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.FederatedAPIDiscovery; import org.wso2.carbon.apimgt.api.model.API; -import org.wso2.carbon.apimgt.api.model.APIIdentifier; import org.wso2.carbon.apimgt.api.model.DiscoveredAPI; import org.wso2.carbon.apimgt.api.model.Environment; -import org.wso2.carbon.apimgt.api.model.Tier; +import org.wso2.carbon.apimgt.api.FederatedAPIBuilder; import org.wso2.carbon.apimgt.impl.kmclient.ApacheFeignHttpClient; +import org.wso2.kong.client.builder.KongAPIBuilderFactory; +import org.wso2.kong.client.builder.KongApiBundle; import org.wso2.kong.client.model.KongAPI; import org.wso2.kong.client.model.KongAPIImplementation; -import org.wso2.kong.client.model.KongAPISpec; import org.wso2.kong.client.model.KongListResponse; import org.wso2.kong.client.model.KongPlugin; import org.wso2.kong.client.model.KongRoute; import org.wso2.kong.client.model.KongService; import org.wso2.kong.client.model.PagedResponse; -import org.wso2.kong.client.util.KongAPIUtil; import java.util.ArrayList; import java.util.Collections; @@ -75,6 +74,7 @@ public class KongFederatedAPIDiscovery implements FederatedAPIDiscovery { private String controlPlaneId; private String authToken; private String deploymentType; + private KongAPIBuilderFactory builderFactory; @Override public void init(Environment environment, String organization) throws APIManagementException { @@ -104,6 +104,9 @@ public void init(Environment environment, String organization) throws APIManagem .logger(new Slf4jLogger(KongKonnectApi.class)) .requestInterceptor(auth) .target(KongKonnectApi.class, adminURL); + + // Initialize the builder factory + this.builderFactory = new KongAPIBuilderFactory(apiGatewayClient, controlPlaneId, organization); } log.debug("Initialization completed Kong Gateway Deployer for environment: " + environment.getName()); } catch (Exception e) { @@ -136,203 +139,128 @@ public List discoverAPI() { List retrievedAPIs = new ArrayList<>(); Set linkedServices = new HashSet<>(); - Gson gson = new Gson(); + Gson gson = new Gson(); + + // Get vhost for URL construction + String vhost = environment.getVhosts() != null && !environment.getVhosts().isEmpty() ? + environment.getVhosts().get(0).getHost() : KongConstants.DEFAULT_VHOST; - // Iterate APIs + // Iterate APIs and build using factory pattern for (KongAPI kongAPI : apis) { - String apiName = kongAPI.getName(); - String apiVersion = kongAPI.getVersion(); - String apiContext = kongAPI.getSlug(); - String apiId = kongAPI.getId(); - - // WSO2 API object - APIIdentifier apiIdentifier = new APIIdentifier(KongConstants.DEFAULT_API_PROVIDER, apiName, - apiVersion); - API api = new API(apiIdentifier); - api.setDisplayName(apiName); - api.setContext(KongAPIUtil.ensureLeadingSlash(apiContext)); - api.setContextTemplate(apiContext != null ? apiContext.toLowerCase() : null); - api.setUuid(apiId); - api.setDescription(kongAPI.getDescription() != null ? kongAPI.getDescription() : ""); - api.setOrganization(organization); - api.setRevision(false); - api.setInitiatedFromGateway(true); - api.setGatewayVendor(KongConstants.DEFAULT_GATEWAY_VENDOR); - api.setGatewayType(environment.getGatewayType()); - - // Fetch and set OAS definition (first spec id if present) - String oas = null; - if (kongAPI.getApiSpecIds() != null && !kongAPI.getApiSpecIds().isEmpty()) { - String specId = kongAPI.getApiSpecIds().get(0); - KongAPISpec spec = apiGatewayClient.getAPISpec(apiId, specId); - if (spec != null && spec.getContent() != null) { - oas = spec.getContent(); // raw OAS (JSON/YAML string) + try { + String apiId = kongAPI.getId(); + + // Get service link for this API + KongAPIImplementation.ServiceLink link = apiToSvc.get(apiId); + if (link == null || link.getControlPlaneId() == null || link.getId() == null) { + log.warn("No service linked for API: " + kongAPI.getName() + " (ID: " + apiId + ")"); + continue; } - } - if (oas != null) { - api.setSwaggerDefinition(oas); - } - - // Map API -> Service via implementations, then fetch Service (V2) and set endpoints - KongService svc = null; - KongAPIImplementation.ServiceLink link = apiToSvc.get(apiId); - if (link != null && link.getControlPlaneId() != null && link.getId() != null) { + String cpId = link.getControlPlaneId(); String serviceId = link.getId(); - svc = apiGatewayClient.getService(cpId, serviceId); - if (svc != null && svc.getHost() != null && svc.getProtocol() != null && - svc.getPort() != null) { - String endpoint = KongAPIUtil.buildEndpointUrl( - svc.getProtocol(), - svc.getHost(), - svc.getPort(), - svc.getPath() - ); - api.setEndpointConfig(KongAPIUtil.buildEndpointConfigJson(endpoint, endpoint, false)); - } - } - - api.setAvailableTiers(new HashSet<>(Collections.singleton(new Tier(KongConstants.DEFAULT_TIER)))); - - String selectedAPILevelRateLimitPolicy = null; - - if (svc == null) { - log.warn("No service found for API: " + apiName + " (ID: " + apiId + ")"); - continue; // Skip this API if no service is linked - } - - // add to linked services to avoid duplicates - linkedServices.add(svc.getId()); - - // Fetch plugin related to services - PagedResponse pluginsResp = apiGatewayClient.listPluginsByServiceId(controlPlaneId, - svc.getId(), KongConstants.DEFAULT_PLUGIN_LIST_LIMIT); - List plugins = (pluginsResp != null && pluginsResp.getData() != null) - ? pluginsResp.getData() : Collections.emptyList(); - - for (KongPlugin plugin : plugins) { - String pluginType = plugin.getName(); - - if (KongConstants.KONG_CORS_PLUGIN_TYPE.equals(pluginType)) { - api.setCorsConfiguration(KongAPIUtil.kongCorsToWso2Cors(plugin)); + + // Fetch service + KongService svc = apiGatewayClient.getService(cpId, serviceId); + if (svc == null) { + log.warn("Service not found for API: " + kongAPI.getName()); continue; } - - if (KongConstants.KONG_RATELIMIT_ADVANCED_PLUGIN_TYPE.equals( - pluginType) && selectedAPILevelRateLimitPolicy == null) { - String p = KongAPIUtil.kongRateLimitingToWso2Policy(plugin); - if (p != null) { - selectedAPILevelRateLimitPolicy = p; - } + + // Mark service as linked + linkedServices.add(svc.getId()); + + // Fetch routes for this service + PagedResponse routesResp = apiGatewayClient.listRoutesByServiceId( + controlPlaneId, svc.getId(), KongConstants.DEFAULT_ROUTE_LIST_LIMIT); + List routes = (routesResp != null && routesResp.getData() != null) ? + routesResp.getData() : Collections.emptyList(); + + // Fetch plugins for this service + PagedResponse pluginsResp = apiGatewayClient.listPluginsByServiceId( + controlPlaneId, svc.getId(), KongConstants.DEFAULT_PLUGIN_LIST_LIMIT); + List plugins = (pluginsResp != null && pluginsResp.getData() != null) ? + pluginsResp.getData() : Collections.emptyList(); + + // Create KongApiBundle + KongApiBundle kongApiBundle = new KongApiBundle(kongAPI, svc, routes, plugins, vhost); + + // Use factory to get appropriate builder + FederatedAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); + if (builder == null) { + log.warn("No builder found for API: " + kongAPI.getName()); continue; } - - if (KongConstants.KONG_RATELIMIT_PLUGIN_TYPE.equals( - pluginType) && selectedAPILevelRateLimitPolicy == null) { - String p = KongAPIUtil.kongRateLimitingStandardToWso2Policy(plugin); - if (p != null) { - selectedAPILevelRateLimitPolicy = p; - } - } - } - if (selectedAPILevelRateLimitPolicy != null) { - api.setApiLevelPolicy(selectedAPILevelRateLimitPolicy); + + // Build the WSO2 API using the builder + API api = builder.build(kongApiBundle, environment, organization); + + // Create DiscoveredAPI with reference artifact + DiscoveredAPI discoveredAPI = new DiscoveredAPI(api, gson.toJson(api)); + retrievedAPIs.add(discoveredAPI); + + } catch (Exception e) { + log.error("Error processing Kong API: " + kongAPI.getName(), e); } - DiscoveredAPI discoveredAPI = new DiscoveredAPI(api, gson.toJson(api)); - retrievedAPIs.add(discoveredAPI); } - // If there are Services without APIs, we can still retrieve them as APIs + // Process Services without API metadata ("raw services") PagedResponse servicesResp = apiGatewayClient.listServices(controlPlaneId, KongConstants.DEFAULT_SERVICE_LIST_LIMIT); List services; if (servicesResp != null && servicesResp.getData() != null) { services = servicesResp.getData(); } else { - services = java.util.Collections.emptyList(); + services = Collections.emptyList(); } for (KongService svc : services) { - // Skip if this service is already linked to an API - if (linkedServices.contains(svc.getId())) { - continue; - } - PagedResponse resp = apiGatewayClient.listRoutesByServiceId(controlPlaneId, svc.getId(), - KongConstants.DEFAULT_ROUTE_LIST_LIMIT); - List routes = (resp != null && resp.getData() != null) ? - resp.getData() : java.util.Collections.emptyList(); - - PagedResponse pluginsResp = apiGatewayClient.listPluginsByServiceId(controlPlaneId, - svc.getId(), KongConstants.DEFAULT_PLUGIN_LIST_LIMIT); - List plugins = (pluginsResp != null && pluginsResp.getData() != null) - ? pluginsResp.getData() : java.util.Collections.emptyList(); - - APIIdentifier apiId = new APIIdentifier(KongConstants.DEFAULT_API_PROVIDER, svc.getName(), - KongConstants.DEFAULT_API_VERSION); - API api = new API(apiId); - api.setDisplayName(svc.getName()); - api.setContext(svc.getName()); - api.setContextTemplate(svc.getName().toLowerCase().replace(" ", "-")); - api.setUuid(svc.getId()); - api.setDescription(""); - api.setOrganization(organization); - api.setRevision(false); - - if (svc.getUpdatedAt() != null) { - api.setLastUpdated(Date.from(java.time.Instant.ofEpochSecond(svc.getUpdatedAt()))); - } - if (svc.getCreatedAt() != null) { - api.setCreatedTime(Long.toString(svc.getCreatedAt())); - } - - api.setInitiatedFromGateway(true); - api.setGatewayVendor(KongConstants.DEFAULT_GATEWAY_VENDOR); - api.setGatewayType(environment.getGatewayType()); - - String vhost = environment.getVhosts() != null && !environment.getVhosts().isEmpty() ? - environment.getVhosts().get(0).getHost() : - KongConstants.DEFAULT_VHOST; - - String apiDefinition = KongAPIUtil.buildOasFromRoutes(svc, routes, vhost); - api.setSwaggerDefinition(apiDefinition); - String endpoint = KongAPIUtil.buildEndpointUrl(svc.getProtocol(), svc.getHost(), svc.getPort(), - svc.getPath()); - api.setEndpointConfig(KongAPIUtil.buildEndpointConfigJson(endpoint, endpoint, false)); - api.setAvailableTiers( - new HashSet<>(java.util.Collections.singleton(new Tier(KongConstants.DEFAULT_TIER)))); - - String selectedAPILevelRateLimitPolicy = null; - - for (KongPlugin plugin : plugins) { - String pluginType = plugin.getName(); - - if (KongConstants.KONG_CORS_PLUGIN_TYPE.equals(pluginType)) { - api.setCorsConfiguration(KongAPIUtil.kongCorsToWso2Cors(plugin)); + try { + // Skip if this service is already linked to an API + if (linkedServices.contains(svc.getId())) { continue; } - - if (KongConstants.KONG_RATELIMIT_ADVANCED_PLUGIN_TYPE.equals( - pluginType) && selectedAPILevelRateLimitPolicy == null) { - String p = KongAPIUtil.kongRateLimitingToWso2Policy(plugin); - if (p != null) { - selectedAPILevelRateLimitPolicy = p; - } + + // Fetch routes for this service + PagedResponse routesResp = apiGatewayClient.listRoutesByServiceId( + controlPlaneId, svc.getId(), KongConstants.DEFAULT_ROUTE_LIST_LIMIT); + List routes = (routesResp != null && routesResp.getData() != null) ? + routesResp.getData() : Collections.emptyList(); + + // Fetch plugins for this service + PagedResponse pluginsResp = apiGatewayClient.listPluginsByServiceId( + controlPlaneId, svc.getId(), KongConstants.DEFAULT_PLUGIN_LIST_LIMIT); + List plugins = (pluginsResp != null && pluginsResp.getData() != null) ? + pluginsResp.getData() : Collections.emptyList(); + + // Create KongApiBundle without API metadata (null api) + KongApiBundle kongApiBundle = new KongApiBundle(null, svc, routes, plugins, vhost); + + // Use factory to get appropriate builder + FederatedAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); + if (builder == null) { + log.warn("No builder found for service: " + svc.getName()); continue; } - - if (KongConstants.KONG_RATELIMIT_PLUGIN_TYPE.equals( - pluginType) && selectedAPILevelRateLimitPolicy == null) { - String p = KongAPIUtil.kongRateLimitingStandardToWso2Policy(plugin); - if (p != null) { - selectedAPILevelRateLimitPolicy = p; - } + + // Build the WSO2 API using the builder + API api = builder.build(kongApiBundle, environment, organization); + + // Set timestamps if available + if (svc.getUpdatedAt() != null) { + api.setLastUpdated(Date.from(java.time.Instant.ofEpochSecond(svc.getUpdatedAt()))); } + if (svc.getCreatedAt() != null) { + api.setCreatedTime(Long.toString(svc.getCreatedAt())); + } + + // Create DiscoveredAPI with reference artifact + DiscoveredAPI discoveredAPI = new DiscoveredAPI(api, gson.toJson(api)); + retrievedAPIs.add(discoveredAPI); + + } catch (Exception e) { + log.error("Error processing Kong service: " + svc.getName(), e); } - if (selectedAPILevelRateLimitPolicy != null) { - api.setApiLevelPolicy(selectedAPILevelRateLimitPolicy); - } - DiscoveredAPI discoveredAPI = new DiscoveredAPI(api, gson.toJson(api)); - retrievedAPIs.add(discoveredAPI); } return retrievedAPIs; } catch (KongGatewayException e) { diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java new file mode 100644 index 0000000..03799dd --- /dev/null +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.kong.client.builder; + +import org.wso2.carbon.apimgt.api.FederatedAPIBuilder; +import org.wso2.kong.client.KongConstants; +import org.wso2.kong.client.KongKonnectApi; +import org.wso2.kong.client.util.KongAPIUtil; + +/** + * Abstract base class for Kong API builders. + * Contains common Kong-specific logic that applies to all Kong API types. + * + * Subclasses (REST, WebSocket, gRPC, etc.) only need to implement: + * - canHandle(): Which API type they support + * - mapSpecificDetails(): Type-specific mapping logic + */ +public abstract class KongAPIBuilder extends FederatedAPIBuilder { + protected KongKonnectApi apiGatewayClient; + protected String controlPlaneId; + protected String organization; + + /** + * Constructor with Kong-specific dependencies. + * All Kong builders need these common dependencies. + */ + public KongAPIBuilder(KongKonnectApi apiGatewayClient, String controlPlaneId, String organization) { + this.apiGatewayClient = apiGatewayClient; + this.controlPlaneId = controlPlaneId; + this.organization = organization; + } + + @Override + protected String getName(KongApiBundle sourceApi) { + if (sourceApi.hasApiMetadata()) { + return sourceApi.getApi().getName(); + } + return sourceApi.getService().getName(); + } + + @Override + protected String getVersion(KongApiBundle sourceApi) { + if (sourceApi.hasApiMetadata() && sourceApi.getApi().getVersion() != null) { + return sourceApi.getApi().getVersion(); + } + return KongConstants.DEFAULT_API_VERSION; + } + + @Override + protected String getContext(KongApiBundle sourceApi) { + if (sourceApi.hasApiMetadata() && sourceApi.getApi().getSlug() != null) { + return KongAPIUtil.ensureLeadingSlash(sourceApi.getApi().getSlug()); + } + return KongAPIUtil.ensureLeadingSlash(sourceApi.getService().getName()); + } + + @Override + protected String getContextTemplate(KongApiBundle sourceApi) { + String context = getContext(sourceApi); + String template = context.startsWith("/") ? context.substring(1) : context; + return template.toLowerCase().replace(" ", "-"); + } + + @Override + protected String getGatewayId(KongApiBundle sourceApi) { + if (sourceApi.hasApiMetadata()) { + return sourceApi.getApi().getId(); + } + return sourceApi.getService().getId(); + } + + @Override + protected String getDescription(KongApiBundle sourceApi) { + if (sourceApi.hasApiMetadata() && sourceApi.getApi().getDescription() != null) { + return sourceApi.getApi().getDescription(); + } + return ""; + } + +} diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java new file mode 100644 index 0000000..7e73a22 --- /dev/null +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.kong.client.builder; + +import org.wso2.carbon.apimgt.api.FederatedBuilderFactory; +import org.wso2.kong.client.KongKonnectApi; + +/** + * Kong-specific implementation of FederatedBuilderFactory. + * Creates and manages builders for different Kong API types (REST, WebSocket, gRPC, etc.). + * + * This factory extends the gateway-agnostic FederatedBuilderFactory and registers + * Kong-specific builders for each API type. + */ +public class KongAPIBuilderFactory extends FederatedBuilderFactory { + + /** + * Constructor initializes the factory with Kong-specific builders. + * + * @param apiGatewayClient Kong Konnect API client for making API calls + * @param controlPlaneId Kong control plane ID + * @param organization WSO2 organization name + */ + public KongAPIBuilderFactory(KongKonnectApi apiGatewayClient, String controlPlaneId, String organization) { + super(); + + // Register Kong-specific builders + registerBuilder(new KongRestAPIBuilder(apiGatewayClient, controlPlaneId, organization)); + registerBuilder(new KongWebSocketAPIBuilder(apiGatewayClient, controlPlaneId, organization)); + + } + +} diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongApiBundle.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongApiBundle.java new file mode 100644 index 0000000..763003f --- /dev/null +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongApiBundle.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.kong.client.builder; + +import java.util.List; + +import org.wso2.kong.client.model.KongAPI; +import org.wso2.kong.client.model.KongPlugin; +import org.wso2.kong.client.model.KongRoute; +import org.wso2.kong.client.model.KongService; + +/** + * Data Transfer Object that combines Kong API metadata with its associated service and routes. + * This allows builders to access all necessary Kong data in one object. + * + * Kong Gateway structure: + * - KongAPI: High-level API metadata (name, version, description, spec) + * - KongService: Backend service configuration (host, port, protocol, path) + * - KongRoutes: Path/routing configuration + * - KongPlugins: Policies and configurations + */ +public class KongApiBundle { + private KongAPI api; + private KongService service; + private List routes; + private List plugins; + private String vhost; + + public KongApiBundle() { + } + + public KongApiBundle(KongAPI api, KongService service, List routes, + List plugins, String vhost) { + this.api = api; + this.service = service; + this.routes = routes; + this.plugins = plugins; + this.vhost = vhost; + } + + public KongAPI getApi() { + return api; + } + + public void setApi(KongAPI api) { + this.api = api; + } + + public KongService getService() { + return service; + } + + public void setService(KongService service) { + this.service = service; + } + + public List getRoutes() { + return routes; + } + + public void setRoutes(List routes) { + this.routes = routes; + } + + public List getPlugins() { + return plugins; + } + + public void setPlugins(List plugins) { + this.plugins = plugins; + } + + public String getVhost() { + return vhost; + } + + public void setVhost(String vhost) { + this.vhost = vhost; + } + + /** + * Checks if this DTO has API metadata (vs being a "raw service"). + */ + public boolean hasApiMetadata() { + return api != null; + } + +} diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongRestAPIBuilder.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongRestAPIBuilder.java new file mode 100644 index 0000000..7deee26 --- /dev/null +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongRestAPIBuilder.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.kong.client.builder; + +import java.util.Collections; +import java.util.HashSet; + +import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.api.model.Environment; +import org.wso2.carbon.apimgt.api.model.Tier; +import org.wso2.kong.client.KongConstants; +import org.wso2.kong.client.KongKonnectApi; +import org.wso2.kong.client.model.KongAPISpec; +import org.wso2.kong.client.model.KongPlugin; +import org.wso2.kong.client.model.KongService; +import org.wso2.kong.client.util.KongAPIUtil; + +/** + * Builder for REST/HTTP/SOAP APIs from Kong Gateway. + * Extends KongAPIBuilder which provides common Kong logic. + * Only implements API-type-specific logic for REST APIs. + */ +public class KongRestAPIBuilder extends KongAPIBuilder { + + public KongRestAPIBuilder(KongKonnectApi apiGatewayClient, String controlPlaneId, String organization) { + super(apiGatewayClient, controlPlaneId, organization); + } + + @Override + public boolean canHandle(KongApiBundle data) { + KongService svc = data.getService(); + if (svc != null && svc.getProtocol() != null) { + String protocol = svc.getProtocol().toLowerCase(); + if (protocol.equals(KongConstants.PROTOCOL_WS) || protocol.equals(KongConstants.PROTOCOL_WSS)) { + return false; + } + } + return true; + } + + @Override + protected void mapSpecificDetails(API api, KongApiBundle data, Environment environment) throws APIManagementException { + api.setType(KongConstants.API_TYPE_HTTP); + api.setTransports(KongConstants.TRANSPORT_HTTP); + + if (data.hasApiMetadata() && data.getApi().getApiSpecIds() != null && + !data.getApi().getApiSpecIds().isEmpty()) { + String specId = data.getApi().getApiSpecIds().get(0); + try { + KongAPISpec spec = apiGatewayClient.getAPISpec(data.getApi().getId(), specId); + if (spec != null && spec.getContent() != null) { + api.setSwaggerDefinition(spec.getContent()); + } + } catch (Exception e) { + String generatedOas = KongAPIUtil.buildOasFromRoutes( + data.getService(), data.getRoutes(), data.getVhost()); + api.setSwaggerDefinition(generatedOas); + } + } else { + String generatedOas = KongAPIUtil.buildOasFromRoutes( + data.getService(), data.getRoutes(), data.getVhost()); + api.setSwaggerDefinition(generatedOas); + } + + KongService svc = data.getService(); + if (svc != null) { + String endpoint = KongAPIUtil.buildEndpointUrl( + svc.getProtocol(), svc.getHost(), svc.getPort(), svc.getPath()); + api.setEndpointConfig(KongAPIUtil.buildEndpointConfigJson(endpoint, endpoint, false)); + } + + api.setAvailableTiers(new HashSet<>(Collections.singleton(new Tier(KongConstants.DEFAULT_TIER)))); + + if (data.getPlugins() != null) { + String selectedRateLimitPolicy = null; + + for (KongPlugin plugin : data.getPlugins()) { + String pluginType = plugin.getName(); + + if (KongConstants.KONG_CORS_PLUGIN_TYPE.equals(pluginType)) { + api.setCorsConfiguration(KongAPIUtil.kongCorsToWso2Cors(plugin)); + continue; + } + + if (KongConstants.KONG_RATELIMIT_ADVANCED_PLUGIN_TYPE.equals(pluginType) + && selectedRateLimitPolicy == null) { + String policy = KongAPIUtil.kongRateLimitingToWso2Policy(plugin); + if (policy != null) { + selectedRateLimitPolicy = policy; + } + continue; + } + + if (KongConstants.KONG_RATELIMIT_PLUGIN_TYPE.equals(pluginType) + && selectedRateLimitPolicy == null) { + String policy = KongAPIUtil.kongRateLimitingStandardToWso2Policy(plugin); + if (policy != null) { + selectedRateLimitPolicy = policy; + } + } + } + + if (selectedRateLimitPolicy != null) { + api.setApiLevelPolicy(selectedRateLimitPolicy); + } + } + } +} diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongWebSocketAPIBuilder.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongWebSocketAPIBuilder.java new file mode 100644 index 0000000..a5b1b33 --- /dev/null +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongWebSocketAPIBuilder.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.kong.client.builder; + +import java.util.Collections; +import java.util.HashSet; + +import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.api.model.Environment; +import org.wso2.carbon.apimgt.api.model.Tier; +import org.wso2.kong.client.KongConstants; +import org.wso2.kong.client.KongKonnectApi; +import org.wso2.kong.client.model.KongPlugin; +import org.wso2.kong.client.model.KongService; +import org.wso2.kong.client.util.KongAPIUtil; + +/** + * Builder for WebSocket APIs from Kong Gateway. + * Extends KongAPIBuilder which provides common Kong logic. + * Only implements WebSocket-specific logic. + */ +public class KongWebSocketAPIBuilder extends KongAPIBuilder { + + public KongWebSocketAPIBuilder(KongKonnectApi apiGatewayClient, String controlPlaneId, String organization) { + super(apiGatewayClient, controlPlaneId, organization); + } + + @Override + public boolean canHandle(KongApiBundle data) { + KongService svc = data.getService(); + if (svc != null && svc.getProtocol() != null) { + String protocol = svc.getProtocol().toLowerCase(); + return protocol.equals(KongConstants.PROTOCOL_WS) || protocol.equals(KongConstants.PROTOCOL_WSS); + } + return false; + } + + @Override + protected void mapSpecificDetails(API api, KongApiBundle data, Environment environment) throws APIManagementException { + api.setType(KongConstants.API_TYPE_WS); + api.setTransports(KongConstants.TRANSPORT_WS); + + KongService svc = data.getService(); + String asyncApiDef = KongAPIUtil.buildDefinitionFromRoutesForAsync( + data.getService(), data.getRoutes(), data.getVhost()); + api.setAsyncApiDefinition(asyncApiDef); + + if (svc != null) { + String endpoint = KongAPIUtil.buildEndpointUrl( + svc.getProtocol(), svc.getHost(), svc.getPort(), svc.getPath()); + api.setEndpointConfig(KongAPIUtil.buildEndpointConfigJson(endpoint, endpoint, false)); + } + + api.setAvailableTiers(new HashSet<>(Collections.singleton(new Tier(KongConstants.DEFAULT_TIER)))); + + if (data.getPlugins() != null) { + String selectedRateLimitPolicy = null; + + for (KongPlugin plugin : data.getPlugins()) { + String pluginType = plugin.getName(); + + if (KongConstants.KONG_CORS_PLUGIN_TYPE.equals(pluginType)) { + api.setCorsConfiguration(KongAPIUtil.kongCorsToWso2Cors(plugin)); + continue; + } + + if (KongConstants.KONG_RATELIMIT_ADVANCED_PLUGIN_TYPE.equals(pluginType) + && selectedRateLimitPolicy == null) { + String policy = KongAPIUtil.kongRateLimitingToWso2Policy(plugin); + if (policy != null) { + selectedRateLimitPolicy = policy; + } + continue; + } + + if (KongConstants.KONG_RATELIMIT_PLUGIN_TYPE.equals(pluginType) + && selectedRateLimitPolicy == null) { + String policy = KongAPIUtil.kongRateLimitingStandardToWso2Policy(plugin); + if (policy != null) { + selectedRateLimitPolicy = policy; + } + } + } + + if (selectedRateLimitPolicy != null) { + api.setApiLevelPolicy(selectedRateLimitPolicy); + } + } + } + +} diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/util/KongAPIUtil.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/util/KongAPIUtil.java index 9c6d1e6..1dc988c 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/util/KongAPIUtil.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/util/KongAPIUtil.java @@ -97,6 +97,19 @@ public static CORSConfiguration kongCorsToWso2Cors(KongPlugin plugin) { return cors; } + public static String determineAPIType(KongService service) { + // 1. Check Service Protocol (The most common indicator) + if (service != null && service.getProtocol() != null) { + String proto = service.getProtocol().toLowerCase(); + if ("ws".equals(proto) || "wss".equals(proto)) { + return "WS"; + } else if ("http".equals(proto) || "https".equals(proto)) { + return "HTTP"; + } + } + return null; + } + public static boolean getBoolean(JsonObject obj, String key, boolean def) { if (obj == null || !obj.has(key) || obj.get(key).isJsonNull()) { return def; @@ -376,6 +389,74 @@ public static String buildOasFromRoutes(KongService svc, List routes, return root.toString(); } + public static String buildDefinitionFromRoutesForAsync(KongService svc, List routes, String vhost) { + JsonObject root = new JsonObject(); + root.addProperty("asyncapi", "2.0.0"); + + JsonObject info = new JsonObject(); + info.addProperty("title", svc.getName() != null ? svc.getName() : "kong-service"); + info.addProperty("version", "v1"); + root.add("info", info); + + JsonObject servers = new JsonObject(); + JsonObject production = new JsonObject(); + production.addProperty("url", "wss://" + vhost); + production.addProperty("protocol", "wss"); + servers.add("production", production); + root.add("servers", servers); + + JsonObject channels = new JsonObject(); + for (KongRoute r : routes) { + List routePaths = (r.getPaths() != null && !r.getPaths().isEmpty()) ? + r.getPaths() : Collections.singletonList("/*"); + + for (String kongPath : routePaths) { + String oasPath = toOasPath(kongPath); + if (channels.has(oasPath)) { + continue; + } + JsonObject channelItem = new JsonObject(); + JsonObject pub = new JsonObject(); + pub.addProperty("operationId", safeOpId(r.getName(), "pub", oasPath)); + channelItem.add("publish", pub); + JsonObject sub = new JsonObject(); + sub.addProperty("operationId", safeOpId(r.getName(), "sub", oasPath)); + channelItem.add("subscribe", sub); + + // Parameters + Matcher m = Pattern.compile("\\{([A-Za-z_][A-Za-z0-9_-]*)\\}").matcher(oasPath); + JsonObject parameters = new JsonObject(); + while (m.find()) { + String pName = m.group(1); + JsonObject pObj = new JsonObject(); + JsonObject schema = new JsonObject(); + schema.addProperty("type", "string"); + pObj.add("schema", schema); + parameters.add(pName, pObj); + } + if (parameters.size() > 0) { + channelItem.add("parameters", parameters); + } + channels.add(oasPath, channelItem); + } + } + + if (channels.entrySet().isEmpty()) { + JsonObject channelItem = new JsonObject(); + JsonObject pub = new JsonObject(); + pub.addProperty("operationId", "pub_default"); + channelItem.add("publish", pub); + JsonObject sub = new JsonObject(); + sub.addProperty("operationId", "sub_default"); + channelItem.add("subscribe", sub); + channels.add("/*", channelItem); + } + + root.add("channels", channels); + root.add("components", new JsonObject()); + return root.toString(); + } + /** * Convert Kong route path value to an OAS path template. Handles: * - plain prefixes like "/get" (returned as-is) From 2a792a7b6e4bd55ba1df7e8a323564944916772e Mon Sep 17 00:00:00 2001 From: janidu-wso2 Date: Wed, 25 Feb 2026 14:18:28 +0530 Subject: [PATCH 2/2] Refactor Kong API builder classes to enhance functionality and support for API discovery --- .../client/KongFederatedAPIDiscovery.java | 7 +- .../kong/client/builder/KongAPIBuilder.java | 70 ++++++++++++++--- .../client/builder/KongAPIBuilderFactory.java | 75 +++++++++++++++++-- 3 files changed, 132 insertions(+), 20 deletions(-) diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java index 6a1ac0f..d3ac80d 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/KongFederatedAPIDiscovery.java @@ -37,9 +37,8 @@ import org.wso2.carbon.apimgt.api.model.API; import org.wso2.carbon.apimgt.api.model.DiscoveredAPI; import org.wso2.carbon.apimgt.api.model.Environment; -import org.wso2.carbon.apimgt.api.FederatedAPIBuilder; import org.wso2.carbon.apimgt.impl.kmclient.ApacheFeignHttpClient; - +import org.wso2.kong.client.builder.KongAPIBuilder; import org.wso2.kong.client.builder.KongAPIBuilderFactory; import org.wso2.kong.client.builder.KongApiBundle; import org.wso2.kong.client.model.KongAPI; @@ -186,7 +185,7 @@ public List discoverAPI() { KongApiBundle kongApiBundle = new KongApiBundle(kongAPI, svc, routes, plugins, vhost); // Use factory to get appropriate builder - FederatedAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); + KongAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); if (builder == null) { log.warn("No builder found for API: " + kongAPI.getName()); continue; @@ -237,7 +236,7 @@ public List discoverAPI() { KongApiBundle kongApiBundle = new KongApiBundle(null, svc, routes, plugins, vhost); // Use factory to get appropriate builder - FederatedAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); + KongAPIBuilder builder = builderFactory.getBuilder(kongApiBundle); if (builder == null) { log.warn("No builder found for service: " + svc.getName()); continue; diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java index 03799dd..5232da0 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilder.java @@ -18,7 +18,12 @@ package org.wso2.kong.client.builder; -import org.wso2.carbon.apimgt.api.FederatedAPIBuilder; +import java.util.UUID; + +import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.api.model.APIIdentifier; +import org.wso2.carbon.apimgt.api.model.Environment; import org.wso2.kong.client.KongConstants; import org.wso2.kong.client.KongKonnectApi; import org.wso2.kong.client.util.KongAPIUtil; @@ -31,7 +36,7 @@ * - canHandle(): Which API type they support * - mapSpecificDetails(): Type-specific mapping logic */ -public abstract class KongAPIBuilder extends FederatedAPIBuilder { +public abstract class KongAPIBuilder { protected KongKonnectApi apiGatewayClient; protected String controlPlaneId; protected String organization; @@ -45,8 +50,43 @@ public KongAPIBuilder(KongKonnectApi apiGatewayClient, String controlPlaneId, St this.controlPlaneId = controlPlaneId; this.organization = organization; } + + /** + * Builds a WSO2 API object from the raw external data. + * + * @param sourceApi The raw data object from the external gateway. + * @param env The environment where the API is discovered. + * @param org The organization context. + * @return The constructed API object. + * @throws APIManagementException If an error occurs during building. + */ + public API build(KongApiBundle sourceApi, Environment env, String org) throws APIManagementException { + // 1. Basic Identification + APIIdentifier apiId = new APIIdentifier(org, getName(sourceApi), getVersion(sourceApi)); + + API api = new API(apiId); + + // 2. Map Common Properties + api.setContext(getContext(sourceApi)); + api.setContextTemplate(getContextTemplate(sourceApi)); + api.setUuid(UUID.randomUUID().toString()); + api.setDescription(getDescription(sourceApi)); + + // 3. Set Standard WSO2 Flags + api.setOrganization(org); + if (env != null) { + api.setGatewayType(env.getGatewayType()); + } + api.setInitiatedFromGateway(true); + api.setRevision(false); + api.setGatewayVendor("external"); + + // 4. Specific Mapping (Delegated to subclasses) + mapSpecificDetails(api, sourceApi, env); + + return api; + } - @Override protected String getName(KongApiBundle sourceApi) { if (sourceApi.hasApiMetadata()) { return sourceApi.getApi().getName(); @@ -54,7 +94,6 @@ protected String getName(KongApiBundle sourceApi) { return sourceApi.getService().getName(); } - @Override protected String getVersion(KongApiBundle sourceApi) { if (sourceApi.hasApiMetadata() && sourceApi.getApi().getVersion() != null) { return sourceApi.getApi().getVersion(); @@ -62,7 +101,6 @@ protected String getVersion(KongApiBundle sourceApi) { return KongConstants.DEFAULT_API_VERSION; } - @Override protected String getContext(KongApiBundle sourceApi) { if (sourceApi.hasApiMetadata() && sourceApi.getApi().getSlug() != null) { return KongAPIUtil.ensureLeadingSlash(sourceApi.getApi().getSlug()); @@ -70,22 +108,19 @@ protected String getContext(KongApiBundle sourceApi) { return KongAPIUtil.ensureLeadingSlash(sourceApi.getService().getName()); } - @Override protected String getContextTemplate(KongApiBundle sourceApi) { String context = getContext(sourceApi); String template = context.startsWith("/") ? context.substring(1) : context; return template.toLowerCase().replace(" ", "-"); } - @Override protected String getGatewayId(KongApiBundle sourceApi) { if (sourceApi.hasApiMetadata()) { return sourceApi.getApi().getId(); } return sourceApi.getService().getId(); } - - @Override + protected String getDescription(KongApiBundle sourceApi) { if (sourceApi.hasApiMetadata() && sourceApi.getApi().getDescription() != null) { return sourceApi.getApi().getDescription(); @@ -93,4 +128,21 @@ protected String getDescription(KongApiBundle sourceApi) { return ""; } + /** + * Maps type-specific details (protocol, endpoints, definitions, etc.) to the API object. + * + * @param api The WSO2 API object to populate. + * @param sourceApi The raw data object. + * @throws APIManagementException If an error occurs during mapping. + */ + protected abstract void mapSpecificDetails(API api, KongApiBundle sourceApi, Environment env) throws APIManagementException; + + /** + * Checks if this builder can handle the given raw data object. + * + * @param sourceApi The raw data object. + * @return True if this builder can handle the object, false otherwise. + */ + public abstract boolean canHandle(KongApiBundle sourceApi); + } diff --git a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java index 7e73a22..ce5b61d 100644 --- a/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java +++ b/kong/controlplane-connector/components/kong.gw.manager/src/main/java/org/wso2/kong/client/builder/KongAPIBuilderFactory.java @@ -18,7 +18,9 @@ package org.wso2.kong.client.builder; -import org.wso2.carbon.apimgt.api.FederatedBuilderFactory; +import java.util.ArrayList; +import java.util.List; + import org.wso2.kong.client.KongKonnectApi; /** @@ -28,8 +30,10 @@ * This factory extends the gateway-agnostic FederatedBuilderFactory and registers * Kong-specific builders for each API type. */ -public class KongAPIBuilderFactory extends FederatedBuilderFactory { - +public class KongAPIBuilderFactory { + + private final List builders; + /** * Constructor initializes the factory with Kong-specific builders. * @@ -37,13 +41,70 @@ public class KongAPIBuilderFactory extends FederatedBuilderFactory(); + initBuilders(apiGatewayClient, controlPlaneId, organization); + } + + private void initBuilders(KongKonnectApi apiGatewayClient, String controlPlaneId, String organization) { registerBuilder(new KongRestAPIBuilder(apiGatewayClient, controlPlaneId, organization)); registerBuilder(new KongWebSocketAPIBuilder(apiGatewayClient, controlPlaneId, organization)); + } + + /** + * @param sourceApi The raw API data from the gateway + * @return The builder that can handle this API type, or exception if unsupported + */ + public KongAPIBuilder getBuilder(KongApiBundle sourceApi) { + for (KongAPIBuilder builder : builders) { + if (builder.canHandle(sourceApi)) { + return builder; + } + } + throw new IllegalStateException( + "No registered builder can handle the given API data"); + } + + /** + * Registers a builder in the factory. + * Subclasses can use this to add builders in their constructor. + * + * @param builder The builder to register + */ + protected void registerBuilder(KongAPIBuilder builder) { + if (builder != null) { + // 1. Check if ANY builder in the list has the same CLASS as the new one + boolean alreadyExists = false; + for (KongAPIBuilder existing : builders) { + if (existing.getClass().equals(builder.getClass())) { + alreadyExists = true; + break; + } + } + if (!alreadyExists) { + builders.add(builder); + } + } + } + + /** + * Gets all registered builders. + * Useful for testing and debugging. + * + * @return List of all registered builders + */ + public List getRegisteredBuilders() { + return new ArrayList<>(builders); // Return a copy for safety + } + + /** + * Gets the count of registered builders. + * + * @return Number of registered builders + */ + public int getBuilderCount() { + return builders.size(); } }