From ba02eb001c93f0411fadf7e314dc11b67b65370f Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Tue, 4 Nov 2025 16:03:43 +0100 Subject: [PATCH] apicurio app to wso2 - draft --- .../cz/trask/migration/AbstractProcess.java | 69 ++++---- src/main/java/cz/trask/migration/ApiSync.java | 7 +- ...mV32.java => ExportApisToWso2FromV32.java} | 14 +- .../impl/v45/ExportAppsToWso2FromV32.java | 152 ++++++++++++++++++ .../model/v32/ApplicationDetail.java | 1 + .../model/v45/ApplicationCreateRequest.java | 51 ++++++ 6 files changed, 245 insertions(+), 49 deletions(-) rename src/main/java/cz/trask/migration/impl/v45/{ExportToWso2FromV32.java => ExportApisToWso2FromV32.java} (93%) create mode 100644 src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java create mode 100644 src/main/java/cz/trask/migration/model/v45/ApplicationCreateRequest.java diff --git a/src/main/java/cz/trask/migration/AbstractProcess.java b/src/main/java/cz/trask/migration/AbstractProcess.java index 1ec64e4..3786a71 100644 --- a/src/main/java/cz/trask/migration/AbstractProcess.java +++ b/src/main/java/cz/trask/migration/AbstractProcess.java @@ -6,10 +6,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.security.KeyStore; import java.util.Base64; import java.util.HashMap; @@ -90,7 +88,8 @@ public abstract class AbstractProcess { protected void setTrustStoreCredentials() { File trustStoreFile = new File(config.getTrustStore().getPath()); if (!trustStoreFile.exists() || !trustStoreFile.isFile()) { - log.warn("Truststore file '{}' does not exist. Skipping truststore setup.", config.getTrustStore().getPath()); + log.warn("Truststore file '{}' does not exist. Skipping truststore setup.", + config.getTrustStore().getPath()); return; } log.info("Setting truststore: " + trustStoreFile.getAbsolutePath()); @@ -98,32 +97,32 @@ public abstract class AbstractProcess { System.setProperty("javax.net.ssl.trustStorePassword", config.getTrustStore().getPassword()); } - private SSLContext createSSLContext(String trustStorePath, String trustStorePassword) - throws Exception { - // Vytvoříme TrustStore - KeyStore trustStore = KeyStore.getInstance("JKS"); - try (FileInputStream fis = new FileInputStream(trustStorePath)) { - trustStore.load(fis, trustStorePassword.toCharArray()); - } - - // Inicializujeme TrustManagerFactory - TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); - tmf.init(trustStore); - - // Vytvoříme SSLContext - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); - - return sslContext; - } - - protected void configureHttpsConnection(HttpsURLConnection connection) - throws Exception { - SSLContext sslContext = createSSLContext( - System.getProperty("javax.net.ssl.trustStore"), + private SSLContext createSSLContext(String trustStorePath, String trustStorePassword) + throws Exception { + // Vytvoříme TrustStore + KeyStore trustStore = KeyStore.getInstance("JKS"); + try (FileInputStream fis = new FileInputStream(trustStorePath)) { + trustStore.load(fis, trustStorePassword.toCharArray()); + } + + // Inicializujeme TrustManagerFactory + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(trustStore); + + // Vytvoříme SSLContext + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + + return sslContext; + } + + protected void configureHttpsConnection(HttpsURLConnection connection) + throws Exception { + SSLContext sslContext = createSSLContext( + System.getProperty("javax.net.ssl.trustStore"), System.getProperty("javax.net.ssl.trustStorePassword")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - } + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + } protected TokenResponse authenticateToWso2AndGetToken() throws Exception { RegisterResponse register = register(config.getSource().getRegistrationApiUrl(), @@ -175,7 +174,7 @@ public abstract class AbstractProcess { String data = "grant_type=password&username=".concat(username).concat("&password=") .concat(URLEncoder.encode(password, "UTF-8")).concat("&scope=").concat(scope); - HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data); + HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data.getBytes()); log.debug("Token response: HTTP Code " + response.getResponseCode() + " Json: " + response.getResponse()); @@ -204,7 +203,7 @@ public abstract class AbstractProcess { String data = "{\"callbackUrl\": \"www.google.lk\",\"clientName\": \"rest_api_publisher" + decodeduser + "\",\"owner\": \"" + decodeduser + "\",\"grantType\": \"password refresh_token\",\"saasApp\": true}"; - HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data); + HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data.getBytes()); log.debug( "Register API response: HTTP Code " + response.getResponseCode() + " Json: " + response.getResponse()); @@ -311,11 +310,9 @@ public abstract class AbstractProcess { * @param data - request data * @throws Exception */ - protected HttpResponse makeDataRequest(String urlStr, Map httpHeaders, String data) + protected HttpResponse makeDataRequest(String urlStr, Map httpHeaders, byte[] data) throws Exception { - byte[] json = data.getBytes(Charset.forName("UTF-8")); - URL url = new URL(urlStr); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); @@ -328,10 +325,12 @@ public abstract class AbstractProcess { con.addRequestProperty(key, httpHeaders.get(key)); } - con.addRequestProperty("Content-Length", "" + json.length); + con.addRequestProperty("Content-Length", "" + data.length); OutputStream out = con.getOutputStream(); - out.write(json); + out.write(data); + out.flush(); + out.close(); InputStream in = con.getInputStream(); diff --git a/src/main/java/cz/trask/migration/ApiSync.java b/src/main/java/cz/trask/migration/ApiSync.java index c3f452d..98ca83b 100644 --- a/src/main/java/cz/trask/migration/ApiSync.java +++ b/src/main/java/cz/trask/migration/ApiSync.java @@ -5,7 +5,8 @@ import org.apache.logging.log4j.Logger; import cz.trask.migration.impl.v32.Wso2AppsToApicurio; import cz.trask.migration.impl.v32.Wso2v32ToApicurio; -import cz.trask.migration.impl.v45.ExportToWso2FromV32; +import cz.trask.migration.impl.v45.ExportApisToWso2FromV32; +import cz.trask.migration.impl.v45.ExportAppsToWso2FromV32; import cz.trask.migration.model.StartParameters; public class ApiSync { @@ -28,7 +29,7 @@ public class ApiSync { imp.process(); } else if (sp.getCommand().equalsIgnoreCase("apicurioApisToWso2")) { log.info("apicurioApisToWso2 command selected."); - ExportToWso2FromV32 exp = new ExportToWso2FromV32(); + ExportApisToWso2FromV32 exp = new ExportApisToWso2FromV32(); exp.process(); } else if (sp.getCommand().equalsIgnoreCase("wso2AppsToApicurio")) { log.info("wso2AppsToApicurio command selected."); @@ -36,7 +37,7 @@ public class ApiSync { apps.process(); } else if (sp.getCommand().equalsIgnoreCase("apicurioAppsToWso2")) { log.info("apicurioAppsToWso2 command selected."); - ExportToWso2FromV32 exp = new ExportToWso2FromV32(); + ExportAppsToWso2FromV32 exp = new ExportAppsToWso2FromV32(); exp.process(); } else { log.error("Unknown command: " + sp.getCommand()); diff --git a/src/main/java/cz/trask/migration/impl/v45/ExportToWso2FromV32.java b/src/main/java/cz/trask/migration/impl/v45/ExportApisToWso2FromV32.java similarity index 93% rename from src/main/java/cz/trask/migration/impl/v45/ExportToWso2FromV32.java rename to src/main/java/cz/trask/migration/impl/v45/ExportApisToWso2FromV32.java index b87af84..7584c66 100644 --- a/src/main/java/cz/trask/migration/impl/v45/ExportToWso2FromV32.java +++ b/src/main/java/cz/trask/migration/impl/v45/ExportApisToWso2FromV32.java @@ -29,8 +29,6 @@ import cz.trask.migration.model.v45.ApiDefinition45; import cz.trask.migration.model.v45.Documents45; import cz.trask.migration.model.v45.EndpointCertificates45; import cz.trask.migration.model.v45.OperationPolicySpecification45; -import io.apicurio.registry.rest.client.RegistryClient; -import io.apicurio.registry.rest.client.RegistryClientFactory; import io.apicurio.registry.rest.v2.beans.ArtifactMetaData; import io.apicurio.registry.rest.v2.beans.ArtifactReference; import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults; @@ -40,16 +38,10 @@ import io.apicurio.registry.rest.v2.beans.VersionSearchResults; import lombok.extern.log4j.Log4j2; @Log4j2 -public class ExportToWso2FromV32 extends AbstractProcess { +public class ExportApisToWso2FromV32 extends AbstractProcess { private final AtomicInteger apiCounter = new AtomicInteger(1); - private final RegistryClient client; - - public ExportToWso2FromV32() throws Exception { - this.client = RegistryClientFactory.create(config.getApicurio().getApiUrl()); - } - /** * Main entry point for the import process. * @@ -100,7 +92,7 @@ public class ExportToWso2FromV32 extends AbstractProcess { config.getApicurio().getDefaultApiGroup(), api.getId(), ver.getVersion()); if (ref != null && !ref.isEmpty()) { log.info("Artifact has {} references", ref.size()); - byte[] data = prepareApiZipFile32to45(client, ver, ref); + byte[] data = prepareApiZipFile32to45(ver, ref); String fileName = api.getName() + "-" + ver.getVersion() + ".zip"; FileOutputStream fos = new FileOutputStream(fileName); @@ -156,7 +148,7 @@ public class ExportToWso2FromV32 extends AbstractProcess { return responseCode; } - public byte[] prepareApiZipFile32to45(RegistryClient client, SearchedVersion ver, List ref) + public byte[] prepareApiZipFile32to45(SearchedVersion ver, List ref) throws Exception { String baseDir = ver.getName() + "-" + ver.getVersion() + "/"; diff --git a/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java b/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java new file mode 100644 index 0000000..5bb1fe8 --- /dev/null +++ b/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java @@ -0,0 +1,152 @@ +package cz.trask.migration.impl.v45; + +import java.io.FileOutputStream; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import cz.trask.migration.AbstractProcess; +import cz.trask.migration.model.HttpResponse; +import cz.trask.migration.model.TokenResponse; +import cz.trask.migration.model.v32.ApplicationDetail; +import cz.trask.migration.model.v45.ApplicationCreateRequest; +import io.apicurio.registry.rest.v2.beans.ArtifactMetaData; +import io.apicurio.registry.rest.v2.beans.ArtifactReference; +import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults; +import io.apicurio.registry.rest.v2.beans.SearchedArtifact; +import io.apicurio.registry.rest.v2.beans.SearchedVersion; +import io.apicurio.registry.rest.v2.beans.VersionSearchResults; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class ExportAppsToWso2FromV32 extends AbstractProcess { + + private final AtomicInteger appCounter = new AtomicInteger(1); + + /** + * Main entry point for the import process. + * + * @throws RuntimeException if any error occurs + */ + public void process() { + try { + log.info("Starting App export to WSO2 from Apicurio..."); + + TokenResponse token = authenticateToWso2AndGetToken(); + + ArtifactSearchResults apps = client.searchArtifacts(ARTIFACT_GROUP_APPLICATIONS, null, null, + null, null, null, null, null, null); + + log.info("Found {} Apps", apps.getCount()); + + int maxThreads = config.getMaxThreads(); + ExecutorService executor = Executors.newFixedThreadPool(maxThreads); + + for (SearchedArtifact app : apps.getArtifacts()) { + final int index = appCounter.getAndIncrement(); + executor.submit(() -> processApp(app, token, index, apps.getCount())); + } + + executor.shutdown(); + if (!executor.awaitTermination(10, TimeUnit.MINUTES)) { + log.warn("Timeout waiting for App import tasks to finish"); + } + log.info("Finished processing Apps."); + } catch (Exception e) { + log.error("Error while exporting Apps.", e); + throw new RuntimeException("Export failed", e); + } + } + + private void processApp(SearchedArtifact app, TokenResponse tokenResponse, int index, int total) { + long start = System.currentTimeMillis(); + + String endpoint = config.getTarget().getDevPortalApiUrl()+ "/v3/applications"; + + try { + log.info("Processing App {} of {}", index, total); + + VersionSearchResults versions = client.listArtifactVersions(ARTIFACT_GROUP_APPLICATIONS, + app.getId(), null, null); + + for (SearchedVersion ver : versions.getVersions()) { + log.info(" - Found version: {}", ver.getVersion()); + + ArtifactMetaData amd = client.getArtifactMetaData(app.getGroupId(), app.getId()); + + byte[] content = client.getContentByGlobalId(amd.getGlobalId()).readAllBytes(); + + if (content != null && content.length > 0) { + ApplicationDetail appDetail = mapper + .readValue(content, ApplicationDetail.class); + ApplicationCreateRequest appCreateRequest = mapAppDetailToCreateRequest(appDetail); + byte[] data = mapper.writeValueAsBytes(appCreateRequest); + log.info(" - Prepared application data for WSO2: {} bytes", data.length); + + // Publish the application data to WSO2 + byte[] decoded = Base64.getDecoder().decode(config.getTarget().getWso2User()); + String decodedstring = new String(decoded); + String[] decodedstringparts = decodedstring.split(":"); + + String username = decodedstringparts[0]; + String password = decodedstringparts[1]; + Map httpHeaders = new HashMap<>(); + + httpHeaders.put("Authorization", "Basic ".concat(Base64.getEncoder() + .encodeToString(username.concat(":").concat(password).getBytes()))); + httpHeaders.put("Content-Type", "application/json"); + + HttpResponse response = makeDataRequest(endpoint, httpHeaders, data); + + if (response.getResponseCode() == 200 || response.getResponseCode() == 201) { + log.info(" - Application version {} imported successfully", ver.getVersion()); + } else { + log.warn(" - Application version {} import failed with response code {}", + ver.getVersion(), response.getResponseCode()); + } + + } + + // String fileName = api.getName() + "-" + ver.getVersion() + ".zip"; + + // FileOutputStream fos = new FileOutputStream(fileName); + // fos.write(data); + // fos.flush(); + // fos.close(); + // System.exit(0); + + // if (data != null && data.length > 0 && fileName != null && + // !fileName.isEmpty()) { + // int responseCode = publishAppToWso2(fileName, data, tokenResponse); + // if (responseCode == 200 || responseCode == 201) { + // log.info(" - API version {} imported successfully", ver.getVersion()); + // } else { + // log.warn(" - API version {} import failed with response code {}", + // ver.getVersion(), + // responseCode); + // } + // } + } + + long end = System.currentTimeMillis(); + log.info("Finished processing App {} of {} in {} ms", index, total, (end - start)); + } catch (Exception e) { + log.error("IO error while importing App {}: {}", app.getId(), e.getMessage(), e); + } + } + + private ApplicationCreateRequest mapAppDetailToCreateRequest(ApplicationDetail appDetail) { + ApplicationCreateRequest request = new ApplicationCreateRequest(); + request.setName(appDetail.getName()); + request.setDescription(appDetail.getDescription()); + request.setThrottlingPolicy(appDetail.getTier()); + request.setTokenType(appDetail.getTokenType()); + request.setGroups(List.of(appDetail.getGroupId())); + return request; + } +} diff --git a/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java b/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java index 5d2843f..2d9bdb6 100644 --- a/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java +++ b/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java @@ -18,6 +18,7 @@ public class ApplicationDetail { private List keys; private Map> keyManagerWiseOAuthApp; private String tier; + private String description; private String status; private String groupId; private String owner; diff --git a/src/main/java/cz/trask/migration/model/v45/ApplicationCreateRequest.java b/src/main/java/cz/trask/migration/model/v45/ApplicationCreateRequest.java new file mode 100644 index 0000000..7ca3e3d --- /dev/null +++ b/src/main/java/cz/trask/migration/model/v45/ApplicationCreateRequest.java @@ -0,0 +1,51 @@ +package cz.trask.migration.model.v45; + +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import javax.validation.constraints.*; +import java.util.List; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApplicationCreateRequest { + + @NotNull + @Size(min = 1, max = 100) + private String name; + + @NotNull + private String throttlingPolicy; + + @Size(max = 512) + private String description; + + @Pattern(regexp = "JWT|OAUTH") + private String tokenType = "JWT"; + + private List groups; + + private Map attributes; + + private List subscriptionScopes; + + @Pattern(regexp = "PRIVATE|SHARED_WITH_ORG") + private String visibility; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class SubscriptionScope { + @NotNull + private String key; + + @NotNull + private String name; + + private List roles; + + private String description; + } +}