From db24917fafcd62272b449e3d3f6849529a3eae6e Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 8 Jul 2025 13:40:12 -0500 Subject: [PATCH 01/44] Update Java toolchain configuration and bump OAuth2 client version --- build.gradle | 97 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 20 deletions(-) diff --git a/build.gradle b/build.gradle index 1c1adca4..f669fa21 100644 --- a/build.gradle +++ b/build.gradle @@ -1,33 +1,54 @@ plugins { id "idea" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" + id "java-library" + id "maven-publish" + id "signing" } group = 'com.vertexvis' -version = '0.11.0' +version = '0.10.0' -allprojects { - group = 'com.vertexvis' - version = '0.11.0' - - repositories { - mavenCentral() - } +repositories { + mavenCentral() } -subprojects { - apply plugin: 'java-library' - - java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } +java { + withJavadocJar() + withSourcesJar() + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) } - - test { - useJUnitPlatform() - testLogging { - events "passed", "skipped", "failed" +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId = 'api-client-java' + from components.java + pom { + name = 'com.vertexvix:api-client-java' + description = 'The Vertex REST API client for Java.' + url = 'https://github.com/Vertexvis/vertex-api-client-java' + licenses { + license { + name = 'MIT' + url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE' + } + } + developers { + developer { + email = 'support@vertexvis.com' + name = 'Vertex Developers' + organizationUrl = 'https://developer.vertexvis.com/' + } + } + scm { + connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' + developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' + url = 'https://github.com/Vertexvis/vertex-api-client-java' + } + } } } } @@ -46,3 +67,39 @@ nexusPublishing { def base64Decode(prop) { return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() } + +signing { + useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) + sign publishing.publications.mavenJava +} + +javadoc { + options.tags = ["http.response.details:a:Http Response Details"] + if(JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } +} + +dependencies { + implementation 'io.swagger:swagger-annotations:1.6.14' + implementation "com.google.code.findbugs:jsr305:3.0.2" + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' + implementation 'com.google.code.gson:gson:2.10' + implementation 'io.gsonfire:gson-fire:1.9.0' + implementation 'org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2' // 1.0.2 doesn't work with okkhttp3 + implementation 'org.apache.commons:commons-lang3:3.15.0' + implementation 'javax.annotation:javax.annotation-api:1.3' + implementation 'org.openapitools:jackson-databind-nullable:0.2.6' + implementation 'info.picocli:picocli:4.7.6' + testImplementation(platform('org.junit:junit-bom:5.10.3')) + testImplementation('org.junit.jupiter:junit-jupiter:5.10.3') + testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} From b97c575254a6d3b5f537f0aba99ab73374715a05 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 8 Jul 2025 16:42:47 -0500 Subject: [PATCH 02/44] use openapi generator --- build.gradle | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/build.gradle b/build.gradle index f669fa21..87520ca2 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id "java-library" id "maven-publish" id "signing" + id "org.openapi.generator" version "7.14.0" } group = 'com.vertexvis' @@ -20,6 +21,47 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } } +compileJava.dependsOn tasks.openApiGenerate +compileTestJava.dependsOn tasks.openApiGenerate + +openApiGenerate { + verbose = false + generatorName = 'java' + generateModelTests = false + generateApiTests = false + generateModelDocumentation = false + remoteInputSpec = 'https://platform.vertexvis.com/spec' + outputDir = "${buildDir}/generated/" + invokerPackage = 'com.vertexvis' + modelPackage = 'com.vertexvis.model' + apiPackage = 'com.vertexvis.api' + configOptions = [ + openApiNullable: "false", + dateLibrary: "java8", + hideGenerationTimestamp: "true", + useRuntimeException: "true" + ] +} + +sourceSets { + main { + java { + srcDirs += [ + "${buildDir}/generated/src/main/java" + ] + } + } +} +tasks.named('sourcesJar') { + dependsOn tasks.openApiGenerate + from sourceSets.main.allJava + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +jar { + from sourceSets.main.allSource + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} publishing { publications { From b25f1e9ca8dafd2db00d18237d52e53076ed843e Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 8 Jul 2025 16:44:25 -0500 Subject: [PATCH 03/44] remove all generated files --- .../com/vertexvis/example/PartCreator.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/examples/src/main/java/com/vertexvis/example/PartCreator.java b/examples/src/main/java/com/vertexvis/example/PartCreator.java index 29772844..c7bbc756 100644 --- a/examples/src/main/java/com/vertexvis/example/PartCreator.java +++ b/examples/src/main/java/com/vertexvis/example/PartCreator.java @@ -32,6 +32,39 @@ public PartCreator(ApiClient client) { this.tiApi = new TranslationInspectionsApi(client); } + public Part createPartFromFile(FileMetadata metadata) throws InterruptedException { + return createPartFromFile(metadata, Collections.emptyMap()); + } + + public Part createPartFromFile(FileMetadata metadata, Map partMetadata) throws InterruptedException { + Part qp = parts.createPart(getCreatePartRequest(metadata.getData().getId(), metadata.getData().getAttributes().getName(), partMetadata)); + UUID partId = + JobPoller.pollUntilJobDone("part", () -> tiApi.getQueuedTranslation(qp.getData().getId())); + return parts.getPart(partId, null); + } + + public CompletableFuture createPartFromFileAsync(UUID id, CreateFileRequest req) { + return createPartFromFileAsync(id, req, Collections.emptyMap()); + } + + public CompletableFuture createPartFromFileAsync(UUID id, CreateFileRequest req, Map metadata) { + CompletableFuture p = + execute(cb -> parts.createPartAsync(getCreatePartRequest(id, req.getData().getAttributes().getName(), metadata), cb)); + CompletableFuture partId = p.thenCompose(qj -> + JobPoller.pollUntilJobDoneAsync("part", () -> + execute(cb -> tiApi.getQueuedTranslationAsync(qj.getData().getId(), cb)))); + + return partId.thenCompose( + pId -> execute((ApiCallback cb) -> parts.getPartAsync(pId, null, cb))); + } + + public CompletableFuture createAssemblyFromRevisions(List revisions, String name) { + CompletableFuture p = + execute(cb -> parts.createPartAsync(createPartAssemblyRequest(revisions, name), cb)); + return p.thenCompose( + pId -> execute((ApiCallback cb) -> parts.getPartAsync(pId.getData().getId(), null, cb))); + } + private static CreatePartRequest getCreatePartRequest(UUID fileId, String partName) { return getCreatePartRequest(fileId, partName, Collections.emptyMap()); } From e5b09ab3e6696678f512dbf3a4c25ffe2e7e5f33 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 9 Jul 2025 10:37:36 -0500 Subject: [PATCH 04/44] add retryoauth test back --- .../com/vertexvis/auth/RetryingOAuthTest.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/test/java/com/vertexvis/auth/RetryingOAuthTest.java diff --git a/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java b/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java new file mode 100644 index 00000000..201b4ce8 --- /dev/null +++ b/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java @@ -0,0 +1,125 @@ +package com.vertexvis.auth; + +import com.vertexvis.ApiClient; +import com.vertexvis.ApiException; +import com.vertexvis.api.PartRevisionsApi; +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +public class RetryingOAuthTest { + final static int numThreads = 10; + private static void startServer(MockWebServer server) throws IOException { + server.setDispatcher(new Dispatcher() { + static final AtomicInteger numCalls = new AtomicInteger(); + static final AtomicInteger tokenVersion = new AtomicInteger(0); + static final AtomicInteger requestCount = new AtomicInteger(0); + + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) throws InterruptedException { + Thread.sleep(200); + int reqNum = requestCount.incrementAndGet(); + System.out.println("[" + reqNum + "] Request to: " + recordedRequest.getPath()); + + if (recordedRequest.getPath().equals("/oauth2/token")) { + if (numCalls.incrementAndGet() > (numThreads / 2)) { + int oldVersion = tokenVersion.get(); + tokenVersion.set(1); + if (oldVersion != 1) { + System.out.println("[" + reqNum + "] **** Token version changed from v" + oldVersion + " to v1 ****"); + } + } + String token = "token-v" + tokenVersion.get(); + System.out.println("[" + reqNum + "] Issuing new token: " + token); + return new MockResponse() + .addHeader("Content-Type", "application/json") + .setBody("{\"access_token\": \"" + token + "\", \"token_type\": \"bearer\"}"); + } + + // Check if it's a part revision delete request + if (recordedRequest.getPath().startsWith("/part-revisions/")) { + String path = recordedRequest.getPath(); + String authHeader = recordedRequest.getHeaders().get("Authorization"); + String expectedToken = "Bearer token-v" + tokenVersion.get(); + System.out.println("[" + reqNum + "] Path: " + path); + System.out.println("[" + reqNum + "] Auth header: " + authHeader); + System.out.println("[" + reqNum + "] Expected token: " + expectedToken); + + if (authHeader == null || !authHeader.equals(expectedToken)) { + System.out.println("[" + reqNum + "] Token mismatch - returning 401"); + return new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .setBody("{\"errors\":[{\"id\":\"" + UUID.randomUUID() + "\",\"status\":\"401\",\"code\":\"Unauthorized\",\"title\":\"Invalid or missing credentials.\"}]}"); + } + + System.out.println("[" + reqNum + "] Request successful"); + return new MockResponse() + .setBody("{\"data\": {\"id\": \"" + UUID.randomUUID() + "\", \"type\": \"queued-job\", \"attributes\": {\"status\": \"complete\", \"created\": \"2023-04-03T12:34:56Z\"}}}"); + } + + return new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + .setBody("{\"errors\":[{\"status\":\"404\",\"code\":\"NotFound\",\"title\":\"Not found\"}]}"); + } + }); + server.start(); + } + + @Test + public void multithreading() throws ApiException, IOException { + final AtomicInteger numFails = new AtomicInteger(); + try (var server = new MockWebServer()) { + startServer(server); + + final String baseUrl = server.url("").toString().replaceAll("/$", ""); + var client = new ApiClient(); + client.setBasePath(baseUrl); + + // Set up RetryingOAuth + var auth = new RetryingOAuth( + baseUrl + "/oauth2/token", + "clientid", + OAuthFlow.APPLICATION, + "clientsecret", + null + ); + client.setHttpClient(new OkHttpClient.Builder() + .addInterceptor(auth) + .build()); + + var prs = new PartRevisionsApi(client); + Thread[] threads = new Thread[numThreads]; + + for (var i = 0; i < numThreads; i++) { + threads[i] = new Thread(() -> { + try { + prs.deletePartRevision(UUID.randomUUID()); + } catch (Exception e) { + e.printStackTrace(); + numFails.incrementAndGet(); + } + }); + threads[i].start(); + } + + for (var i = 0; i < numThreads; i++) { + threads[i].join(); + } + + Assertions.assertEquals(0, numFails.get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file From 10f315ee65856f96c1a8b9ec7e41cf9b8dd16a80 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 9 Jul 2025 10:38:16 -0500 Subject: [PATCH 05/44] ignore files for genration --- build.gradle | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 87520ca2..cba389b3 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.vertexvis' -version = '0.10.0' +version = '0.11.0' repositories { mavenCentral() @@ -21,8 +21,6 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } } -compileJava.dependsOn tasks.openApiGenerate -compileTestJava.dependsOn tasks.openApiGenerate openApiGenerate { verbose = false @@ -39,8 +37,9 @@ openApiGenerate { openApiNullable: "false", dateLibrary: "java8", hideGenerationTimestamp: "true", - useRuntimeException: "true" + useRuntimeException: "true", ] + ignoreFileOverride = "${projectDir}/.openapi-generator-ignore" } sourceSets { @@ -52,6 +51,11 @@ sourceSets { } } } + + +compileJava.dependsOn tasks.openApiGenerate +compileTestJava.dependsOn tasks.openApiGenerate + tasks.named('sourcesJar') { dependsOn tasks.openApiGenerate from sourceSets.main.allJava @@ -111,7 +115,12 @@ def base64Decode(prop) { } signing { - useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) + def hasSigningKey = project.hasProperty("signingKey") + def hasSigningPassword = project.hasProperty("signingPassword") + required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") } + if (hasSigningKey && hasSigningPassword) { + useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) + } sign publishing.publications.mavenJava } @@ -122,6 +131,19 @@ javadoc { } } +tasks.withType(Sign) { + dependsOn tasks.withType(GenerateModuleMetadata) + dependsOn tasks.withType(Jar) +} + +tasks.withType(PublishToMavenLocal) { + dependsOn tasks.withType(Sign) +} + +javadoc { + dependsOn tasks.openApiGenerate +} + dependencies { implementation 'io.swagger:swagger-annotations:1.6.14' implementation "com.google.code.findbugs:jsr305:3.0.2" From 6d13bd9f6a92f7b49fe26dab67155f82915e7a7d Mon Sep 17 00:00:00 2001 From: gbiv Date: Thu, 10 Jul 2025 14:34:33 -0500 Subject: [PATCH 06/44] use generated classes --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cba389b3..1c22ce80 100644 --- a/build.gradle +++ b/build.gradle @@ -149,7 +149,7 @@ dependencies { implementation "com.google.code.findbugs:jsr305:3.0.2" implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' - implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.code.gson:gson:2.13.1' implementation 'io.gsonfire:gson-fire:1.9.0' implementation 'org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2' // 1.0.2 doesn't work with okkhttp3 implementation 'org.apache.commons:commons-lang3:3.15.0' From 776d28cdd3234254a3ba9738a9d09518ce6e08c9 Mon Sep 17 00:00:00 2001 From: gbiv Date: Mon, 14 Jul 2025 16:03:28 -0500 Subject: [PATCH 07/44] multi module project --- build.gradle | 155 ++++++--------------------------------------------- 1 file changed, 17 insertions(+), 138 deletions(-) diff --git a/build.gradle b/build.gradle index 1c22ce80..1c1adca4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,100 +1,33 @@ plugins { id "idea" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" - id "java-library" - id "maven-publish" - id "signing" - id "org.openapi.generator" version "7.14.0" } group = 'com.vertexvis' version = '0.11.0' -repositories { - mavenCentral() -} - -java { - withJavadocJar() - withSourcesJar() - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) +allprojects { + group = 'com.vertexvis' + version = '0.11.0' + + repositories { + mavenCentral() } } -openApiGenerate { - verbose = false - generatorName = 'java' - generateModelTests = false - generateApiTests = false - generateModelDocumentation = false - remoteInputSpec = 'https://platform.vertexvis.com/spec' - outputDir = "${buildDir}/generated/" - invokerPackage = 'com.vertexvis' - modelPackage = 'com.vertexvis.model' - apiPackage = 'com.vertexvis.api' - configOptions = [ - openApiNullable: "false", - dateLibrary: "java8", - hideGenerationTimestamp: "true", - useRuntimeException: "true", - ] - ignoreFileOverride = "${projectDir}/.openapi-generator-ignore" -} - -sourceSets { - main { - java { - srcDirs += [ - "${buildDir}/generated/src/main/java" - ] +subprojects { + apply plugin: 'java-library' + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) } } -} - - -compileJava.dependsOn tasks.openApiGenerate -compileTestJava.dependsOn tasks.openApiGenerate - -tasks.named('sourcesJar') { - dependsOn tasks.openApiGenerate - from sourceSets.main.allJava - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -jar { - from sourceSets.main.allSource - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -publishing { - publications { - mavenJava(MavenPublication) { - artifactId = 'api-client-java' - from components.java - pom { - name = 'com.vertexvix:api-client-java' - description = 'The Vertex REST API client for Java.' - url = 'https://github.com/Vertexvis/vertex-api-client-java' - licenses { - license { - name = 'MIT' - url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE' - } - } - developers { - developer { - email = 'support@vertexvis.com' - name = 'Vertex Developers' - organizationUrl = 'https://developer.vertexvis.com/' - } - } - scm { - connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' - developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' - url = 'https://github.com/Vertexvis/vertex-api-client-java' - } - } + + test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" } } } @@ -113,57 +46,3 @@ nexusPublishing { def base64Decode(prop) { return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() } - -signing { - def hasSigningKey = project.hasProperty("signingKey") - def hasSigningPassword = project.hasProperty("signingPassword") - required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") } - if (hasSigningKey && hasSigningPassword) { - useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) - } - sign publishing.publications.mavenJava -} - -javadoc { - options.tags = ["http.response.details:a:Http Response Details"] - if(JavaVersion.current().isJava8Compatible()) { - options.addStringOption('Xdoclint:none', '-quiet') - } -} - -tasks.withType(Sign) { - dependsOn tasks.withType(GenerateModuleMetadata) - dependsOn tasks.withType(Jar) -} - -tasks.withType(PublishToMavenLocal) { - dependsOn tasks.withType(Sign) -} - -javadoc { - dependsOn tasks.openApiGenerate -} - -dependencies { - implementation 'io.swagger:swagger-annotations:1.6.14' - implementation "com.google.code.findbugs:jsr305:3.0.2" - implementation 'com.squareup.okhttp3:okhttp:4.12.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' - implementation 'com.google.code.gson:gson:2.13.1' - implementation 'io.gsonfire:gson-fire:1.9.0' - implementation 'org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2' // 1.0.2 doesn't work with okkhttp3 - implementation 'org.apache.commons:commons-lang3:3.15.0' - implementation 'javax.annotation:javax.annotation-api:1.3' - implementation 'org.openapitools:jackson-databind-nullable:0.2.6' - implementation 'info.picocli:picocli:4.7.6' - testImplementation(platform('org.junit:junit-bom:5.10.3')) - testImplementation('org.junit.jupiter:junit-jupiter:5.10.3') - testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") -} - -test { - useJUnitPlatform() - testLogging { - events "passed", "skipped", "failed" - } -} From d8162ef40db9025d53abc5c504fa243d7ae8d151 Mon Sep 17 00:00:00 2001 From: gbiv Date: Mon, 14 Jul 2025 17:54:31 -0500 Subject: [PATCH 08/44] remove unused configuration and RetryingOAuth implementation --- .../com/vertexvis/auth/RetryingOAuthTest.java | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 src/test/java/com/vertexvis/auth/RetryingOAuthTest.java diff --git a/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java b/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java deleted file mode 100644 index 201b4ce8..00000000 --- a/src/test/java/com/vertexvis/auth/RetryingOAuthTest.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.vertexvis.auth; - -import com.vertexvis.ApiClient; -import com.vertexvis.ApiException; -import com.vertexvis.api.PartRevisionsApi; -import okhttp3.OkHttpClient; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -public class RetryingOAuthTest { - final static int numThreads = 10; - private static void startServer(MockWebServer server) throws IOException { - server.setDispatcher(new Dispatcher() { - static final AtomicInteger numCalls = new AtomicInteger(); - static final AtomicInteger tokenVersion = new AtomicInteger(0); - static final AtomicInteger requestCount = new AtomicInteger(0); - - @NotNull - @Override - public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) throws InterruptedException { - Thread.sleep(200); - int reqNum = requestCount.incrementAndGet(); - System.out.println("[" + reqNum + "] Request to: " + recordedRequest.getPath()); - - if (recordedRequest.getPath().equals("/oauth2/token")) { - if (numCalls.incrementAndGet() > (numThreads / 2)) { - int oldVersion = tokenVersion.get(); - tokenVersion.set(1); - if (oldVersion != 1) { - System.out.println("[" + reqNum + "] **** Token version changed from v" + oldVersion + " to v1 ****"); - } - } - String token = "token-v" + tokenVersion.get(); - System.out.println("[" + reqNum + "] Issuing new token: " + token); - return new MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("{\"access_token\": \"" + token + "\", \"token_type\": \"bearer\"}"); - } - - // Check if it's a part revision delete request - if (recordedRequest.getPath().startsWith("/part-revisions/")) { - String path = recordedRequest.getPath(); - String authHeader = recordedRequest.getHeaders().get("Authorization"); - String expectedToken = "Bearer token-v" + tokenVersion.get(); - System.out.println("[" + reqNum + "] Path: " + path); - System.out.println("[" + reqNum + "] Auth header: " + authHeader); - System.out.println("[" + reqNum + "] Expected token: " + expectedToken); - - if (authHeader == null || !authHeader.equals(expectedToken)) { - System.out.println("[" + reqNum + "] Token mismatch - returning 401"); - return new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED) - .setBody("{\"errors\":[{\"id\":\"" + UUID.randomUUID() + "\",\"status\":\"401\",\"code\":\"Unauthorized\",\"title\":\"Invalid or missing credentials.\"}]}"); - } - - System.out.println("[" + reqNum + "] Request successful"); - return new MockResponse() - .setBody("{\"data\": {\"id\": \"" + UUID.randomUUID() + "\", \"type\": \"queued-job\", \"attributes\": {\"status\": \"complete\", \"created\": \"2023-04-03T12:34:56Z\"}}}"); - } - - return new MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) - .setBody("{\"errors\":[{\"status\":\"404\",\"code\":\"NotFound\",\"title\":\"Not found\"}]}"); - } - }); - server.start(); - } - - @Test - public void multithreading() throws ApiException, IOException { - final AtomicInteger numFails = new AtomicInteger(); - try (var server = new MockWebServer()) { - startServer(server); - - final String baseUrl = server.url("").toString().replaceAll("/$", ""); - var client = new ApiClient(); - client.setBasePath(baseUrl); - - // Set up RetryingOAuth - var auth = new RetryingOAuth( - baseUrl + "/oauth2/token", - "clientid", - OAuthFlow.APPLICATION, - "clientsecret", - null - ); - client.setHttpClient(new OkHttpClient.Builder() - .addInterceptor(auth) - .build()); - - var prs = new PartRevisionsApi(client); - Thread[] threads = new Thread[numThreads]; - - for (var i = 0; i < numThreads; i++) { - threads[i] = new Thread(() -> { - try { - prs.deletePartRevision(UUID.randomUUID()); - } catch (Exception e) { - e.printStackTrace(); - numFails.incrementAndGet(); - } - }); - threads[i].start(); - } - - for (var i = 0; i < numThreads; i++) { - threads[i].join(); - } - - Assertions.assertEquals(0, numFails.get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file From 5e6fcc4d3e9768a3d4584acc97c0e5fd8f1b664c Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 10:31:42 -0500 Subject: [PATCH 09/44] add GitHub Packages repository configuration for publishing snapshots --- .github/workflows/pr-snapshot.yml | 30 +++++++++++++++++++++++++++ api-client-library/build.gradle | 10 +++++++++ build.gradle | 11 +++++++++- openapi-generator-plugin/build.gradle | 10 +++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-snapshot.yml diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml new file mode 100644 index 00000000..adb937f0 --- /dev/null +++ b/.github/workflows/pr-snapshot.yml @@ -0,0 +1,30 @@ +name: PR Snapshot + +on: + push: + branches-ignore: + - main + +jobs: + build-and-publish-snapshot: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build and Publish Snapshot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} + run: | + # Build the plugin first + ./gradlew :openapi-generator-plugin:build + + # Publish snapshots to GitHub Packages + ./gradlew -PisSnapshot=true :openapi-generator-plugin:publish :api-client-library:publish diff --git a/api-client-library/build.gradle b/api-client-library/build.gradle index c2bbde60..da40188d 100644 --- a/api-client-library/build.gradle +++ b/api-client-library/build.gradle @@ -89,6 +89,16 @@ jar { } publishing { + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" + } + } + } publications { maven(MavenPublication) { artifactId = 'api-client-java' diff --git a/build.gradle b/build.gradle index 1c1adca4..991e58a7 100644 --- a/build.gradle +++ b/build.gradle @@ -8,10 +8,19 @@ version = '0.11.0' allprojects { group = 'com.vertexvis' - version = '0.11.0' + version = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() ? + '0.11.0-SNAPSHOT' : '0.11.0' repositories { mavenCentral() + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" + } + } } } diff --git a/openapi-generator-plugin/build.gradle b/openapi-generator-plugin/build.gradle index 18d4e68a..821aa991 100644 --- a/openapi-generator-plugin/build.gradle +++ b/openapi-generator-plugin/build.gradle @@ -21,6 +21,16 @@ java { } publishing { + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" + } + } + } publications { maven(MavenPublication) { artifactId = 'openapi-generator-plugin' From fc6e1c87afec935242c34f966067f4184079f8f9 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 15:32:37 -0500 Subject: [PATCH 10/44] update GitHub Actions workflow to use dynamic signing parameters and improve snapshot publishing process; upgrade Nexus publish plugin to version 2.0.0 --- .github/workflows/pr-snapshot.yml | 11 ++++++++++- build.gradle | 9 ++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index adb937f0..3a1ce5cc 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -22,9 +22,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} + # Optional signing environment variables for consistency with publish workflow + SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_signingKey || '' }} + SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }} run: | # Build the plugin first ./gradlew :openapi-generator-plugin:build + # Set up parameters based on available secrets + GRADLE_PARAMS="-PisSnapshot=true" + if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then + GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD" + fi + # Publish snapshots to GitHub Packages - ./gradlew -PisSnapshot=true :openapi-generator-plugin:publish :api-client-library:publish + ./gradlew $GRADLE_PARAMS :openapi-generator-plugin:publish :api-client-library:publish diff --git a/build.gradle b/build.gradle index 991e58a7..27a33ba6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "idea" - id "io.github.gradle-nexus.publish-plugin" version "1.1.0" + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } group = 'com.vertexvis' @@ -42,10 +42,13 @@ subprojects { } nexusPublishing { + repositories( + sonatype() + ) repositories { sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) username = project.hasProperty("ossrhUsername") ? project.ossrhUsername : "" password = project.hasProperty("ossrhPassword") ? project.ossrhPassword : "" } From 9498f26517a7eacd11c88f67cf7cb7400f9f229f Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 16:10:16 -0500 Subject: [PATCH 11/44] refactor build.gradle to use dynamic project versioning and improve dependency management for openapi-generator-plugin --- api-client-library/build.gradle | 2 +- build.gradle | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/api-client-library/build.gradle b/api-client-library/build.gradle index da40188d..2a63e2c2 100644 --- a/api-client-library/build.gradle +++ b/api-client-library/build.gradle @@ -1,6 +1,6 @@ buildscript{ dependencies { - classpath files("$rootDir/openapi-generator-plugin/build/libs/openapi-generator-plugin-0.11.0.jar") + classpath files("$rootDir/openapi-generator-plugin/build/libs/openapi-generator-plugin-${rootProject.version}.jar") } } plugins { diff --git a/build.gradle b/build.gradle index 27a33ba6..f9ef3344 100644 --- a/build.gradle +++ b/build.gradle @@ -3,13 +3,20 @@ plugins { id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } +// Define version once at the root level +def projectVersion = '0.11.0' +def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() +version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion + +// Set group at the root level group = 'com.vertexvis' -version = '0.11.0' allprojects { group = 'com.vertexvis' - version = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() ? - '0.11.0-SNAPSHOT' : '0.11.0' + version = rootProject.version + + // Make project version accessible as a project property for buildscript blocks + ext.projectVersion = projectVersion repositories { mavenCentral() @@ -41,10 +48,19 @@ subprojects { } } +// Configure dependencies between projects +project(':api-client-library').afterEvaluate { + // Ensure openapi-generator-plugin is built and published before api-client-library tasks + it.tasks.withType(AbstractCompile) { + it.dependsOn project(':openapi-generator-plugin').tasks.named('build') + } + + it.tasks.named("openApiGenerate").configure { + dependsOn project(':openapi-generator-plugin').tasks.named('build') + } +} + nexusPublishing { - repositories( - sonatype() - ) repositories { sonatype { nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) From 65ca762e889f970db0a51946df7ba917a59be273 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 16:57:30 -0500 Subject: [PATCH 12/44] add Sonatype credentials to GitHub Actions workflow for snapshot publishing --- .github/workflows/pr-snapshot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 3a1ce5cc..20d08b7d 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -22,6 +22,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} + # Sonatype credentials + OSSRH_USERNAME: ${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} + OSSRH_PASSWORD: ${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} # Optional signing environment variables for consistency with publish workflow SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_signingKey || '' }} SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }} From 2de217c248b76963acfe0faaab6db10a9e2d0d7f Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 17:04:19 -0500 Subject: [PATCH 13/44] update GitHub Actions workflow to include Sonatype credentials in snapshot publishing parameters --- .github/workflows/pr-snapshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 20d08b7d..2d9a2068 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -35,7 +35,7 @@ jobs: # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then - GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD" + GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }}" fi # Publish snapshots to GitHub Packages From 8294006d6e7afb87bde7e0a1c3cc07c9f2bc4b35 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 17:07:31 -0500 Subject: [PATCH 14/44] refactor GitHub Actions workflow to separate parameters for improved readability in snapshot publishing --- .github/workflows/pr-snapshot.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 2d9a2068..ca10a8bd 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -35,8 +35,11 @@ jobs: # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then - GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }}" + GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD" fi # Publish snapshots to GitHub Packages - ./gradlew $GRADLE_PARAMS :openapi-generator-plugin:publish :api-client-library:publish + ./gradlew \ + -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ + -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ + $GRADLE_PARAMS :openapi-generator-plugin:publish :api-client-library:publish From 669b0e01bc685dd69d2e2c0f44c659c88c7f3437 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 17:12:33 -0500 Subject: [PATCH 15/44] remove redundant build step for openapi-generator-plugin in snapshot publishing workflow --- .github/workflows/pr-snapshot.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index ca10a8bd..68543131 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -29,9 +29,6 @@ jobs: SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_signingKey || '' }} SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }} run: | - # Build the plugin first - ./gradlew :openapi-generator-plugin:build - # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then @@ -39,7 +36,7 @@ jobs: fi # Publish snapshots to GitHub Packages - ./gradlew \ + ./gradlew -PisSnapshot=true \ -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ $GRADLE_PARAMS :openapi-generator-plugin:publish :api-client-library:publish From f41cce21ca9314c20305239134b8c2b5d6368c0e Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 18:00:37 -0500 Subject: [PATCH 16/44] refactor: qualify Thread.currentThread() with java.lang to avoid ambiguity --- build.gradle | 13 +++++-------- .../example/CreateAssemblyFromRevisionsExample.java | 2 +- .../CreatePartRevisionsWithMetadataExample.java | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index f9ef3344..212a7fc5 100644 --- a/build.gradle +++ b/build.gradle @@ -48,14 +48,11 @@ subprojects { } } -// Configure dependencies between projects -project(':api-client-library').afterEvaluate { - // Ensure openapi-generator-plugin is built and published before api-client-library tasks - it.tasks.withType(AbstractCompile) { - it.dependsOn project(':openapi-generator-plugin').tasks.named('build') - } - - it.tasks.named("openApiGenerate").configure { +// Simplified approach to handle project dependencies +gradle.projectsEvaluated { + // This block runs after all projects have been evaluated + project(':api-client-library').tasks.named('openApiGenerate') { + // First, make sure the plugin is built dependsOn project(':openapi-generator-plugin').tasks.named('build') } } diff --git a/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java b/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java index ba8841db..8a363d64 100644 --- a/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java +++ b/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java @@ -86,7 +86,7 @@ public void run() { } catch (InterruptedException e) { logger.severe(e.getMessage()); // Restore interrupted state... - Thread.currentThread().interrupt(); + java.lang.Thread.currentThread().interrupt(); } } diff --git a/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java b/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java index 3798f009..88dc2ea9 100644 --- a/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java +++ b/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java @@ -87,7 +87,7 @@ public void run() { } catch (InterruptedException e) { logger.severe(e.getMessage()); // Restore interrupted state... - Thread.currentThread().interrupt(); + java.lang.Thread.currentThread().interrupt(); } } From db4c47944814275236ccdf9ef106ff0ab785e500 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 18:28:05 -0500 Subject: [PATCH 17/44] refactor: move plugin build step to the beginning of the workflow for clarity --- .github/workflows/pr-snapshot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 68543131..0dab051a 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -29,6 +29,9 @@ jobs: SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_signingKey || '' }} SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }} run: | + # Build the plugin first + ./gradlew :openapi-generator-plugin:build + # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then From 6c209af83a8a9df5e2fd985536d71785521c46e4 Mon Sep 17 00:00:00 2001 From: gbiv Date: Tue, 22 Jul 2025 18:34:14 -0500 Subject: [PATCH 18/44] refactor: add publish step for openapi-generator-plugin to Maven Local --- .github/workflows/pr-snapshot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 0dab051a..bc65aa4a 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -32,6 +32,9 @@ jobs: # Build the plugin first ./gradlew :openapi-generator-plugin:build + # Publish the plugin to Maven Local so api-client-library can use it + ./gradlew :openapi-generator-plugin:publishToMavenLocal + # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then From 21bf61d976fd5920894a5df9df37ad7f568e0ac6 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 08:53:06 -0500 Subject: [PATCH 19/44] checkpoint --- .github/workflows/publish.yml | 4 ++-- build.gradle | 4 +++- .../java/com/vertexvis/codegen/VertexJavaClientCodegen.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2f66e133..f320324e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,8 +35,8 @@ jobs: ./gradlew \ -P signingKey=${{ secrets.ORG_GRADLE_PROJECT_signingKey }} \ -P signingPassword=${{ secrets.ORG_GRADLE_PROJECT_signingPassword }} \ - -P ossrhUsername=${{ secrets.OSSRH_USERNAME }} \ - -P ossrhPassword=${{ secrets.OSSRH_PASSWORD }} \ + -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ + -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ :api-client-library:publishToSonatype \ :api-client-library:closeAndReleaseSonatypeStagingRepository ./scripts/publish.sh diff --git a/build.gradle b/build.gradle index 212a7fc5..de406256 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ allprojects { ext.projectVersion = projectVersion repositories { + mavenLocal() // Look for local artifacts first mavenCentral() maven { name = "GitHubPackages" @@ -48,7 +49,7 @@ subprojects { } } -// Simplified approach to handle project dependencies +// Ensure proper build order between projects gradle.projectsEvaluated { // This block runs after all projects have been evaluated project(':api-client-library').tasks.named('openApiGenerate') { @@ -57,6 +58,7 @@ gradle.projectsEvaluated { } } + nexusPublishing { repositories { sonatype { diff --git a/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java b/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java index e3a99c52..b7ee2605 100644 --- a/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java +++ b/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java @@ -15,7 +15,7 @@ /** * Custom Java client codegen that supports conditional validation skipping. */ -public class VertexJavaClientCodegen extends JavaClientCodegen implements CodegenConfig { +public class VertexJavaClientCodegen extends JavaClientCodegen { private static final Logger LOGGER = LoggerFactory.getLogger(VertexJavaClientCodegen.class); /** Configuration option key for specifying models to skip validation for. */ From 1a3a9d55019d789a808e26b5ea82a11001f0685b Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 12:14:24 -0500 Subject: [PATCH 20/44] refactor: remove synchronous part creation methods for cleaner API --- .../com/vertexvis/example/PartCreator.java | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/examples/src/main/java/com/vertexvis/example/PartCreator.java b/examples/src/main/java/com/vertexvis/example/PartCreator.java index c7bbc756..29772844 100644 --- a/examples/src/main/java/com/vertexvis/example/PartCreator.java +++ b/examples/src/main/java/com/vertexvis/example/PartCreator.java @@ -32,39 +32,6 @@ public PartCreator(ApiClient client) { this.tiApi = new TranslationInspectionsApi(client); } - public Part createPartFromFile(FileMetadata metadata) throws InterruptedException { - return createPartFromFile(metadata, Collections.emptyMap()); - } - - public Part createPartFromFile(FileMetadata metadata, Map partMetadata) throws InterruptedException { - Part qp = parts.createPart(getCreatePartRequest(metadata.getData().getId(), metadata.getData().getAttributes().getName(), partMetadata)); - UUID partId = - JobPoller.pollUntilJobDone("part", () -> tiApi.getQueuedTranslation(qp.getData().getId())); - return parts.getPart(partId, null); - } - - public CompletableFuture createPartFromFileAsync(UUID id, CreateFileRequest req) { - return createPartFromFileAsync(id, req, Collections.emptyMap()); - } - - public CompletableFuture createPartFromFileAsync(UUID id, CreateFileRequest req, Map metadata) { - CompletableFuture p = - execute(cb -> parts.createPartAsync(getCreatePartRequest(id, req.getData().getAttributes().getName(), metadata), cb)); - CompletableFuture partId = p.thenCompose(qj -> - JobPoller.pollUntilJobDoneAsync("part", () -> - execute(cb -> tiApi.getQueuedTranslationAsync(qj.getData().getId(), cb)))); - - return partId.thenCompose( - pId -> execute((ApiCallback cb) -> parts.getPartAsync(pId, null, cb))); - } - - public CompletableFuture createAssemblyFromRevisions(List revisions, String name) { - CompletableFuture p = - execute(cb -> parts.createPartAsync(createPartAssemblyRequest(revisions, name), cb)); - return p.thenCompose( - pId -> execute((ApiCallback cb) -> parts.getPartAsync(pId.getData().getId(), null, cb))); - } - private static CreatePartRequest getCreatePartRequest(UUID fileId, String partName) { return getCreatePartRequest(fileId, partName, Collections.emptyMap()); } From c66f85247f26e1e3168b51bc3146e557f2a8b6a8 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 12:26:50 -0500 Subject: [PATCH 21/44] refactor: remove openapi-generator-plugin publish step from PR snapshot workflow --- .github/workflows/pr-snapshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index bc65aa4a..976a21d2 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -45,4 +45,4 @@ jobs: ./gradlew -PisSnapshot=true \ -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ - $GRADLE_PARAMS :openapi-generator-plugin:publish :api-client-library:publish + $GRADLE_PARAMS :api-client-library:publish From f573aba30c9c5f6ccb7bddc688b21c8f2c4ad3e8 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 12:54:21 -0500 Subject: [PATCH 22/44] refactor: update publish command to target Sonatype in PR snapshot workflow --- .github/workflows/pr-snapshot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 976a21d2..9676b452 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -45,4 +45,4 @@ jobs: ./gradlew -PisSnapshot=true \ -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ - $GRADLE_PARAMS :api-client-library:publish + $GRADLE_PARAMS :api-client-library:publishToSonatype From a53a557444092417d4995c23f77f649a519187e0 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 14:49:58 -0500 Subject: [PATCH 23/44] refactor: update build.gradle to use mavenLocal and clean up publish task dependencies --- api-client-library/build.gradle | 12 ++++++++---- openapi-generator-plugin/build.gradle | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api-client-library/build.gradle b/api-client-library/build.gradle index 2a63e2c2..e5e2eebf 100644 --- a/api-client-library/build.gradle +++ b/api-client-library/build.gradle @@ -1,6 +1,10 @@ buildscript{ + repositories { + mavenLocal() + mavenCentral() + } dependencies { - classpath files("$rootDir/openapi-generator-plugin/build/libs/openapi-generator-plugin-${rootProject.version}.jar") + classpath "com.vertexvis:openapi-generator-plugin:${rootProject.version}" } } plugins { @@ -156,6 +160,6 @@ tasks.withType(Sign) { dependsOn tasks.withType(Jar) } -tasks.withType(PublishToMavenLocal) { - dependsOn tasks.withType(Sign) -} +// tasks.withType(PublishToMavenLocal) { +// dependsOn tasks.withType(Sign) +// } diff --git a/openapi-generator-plugin/build.gradle b/openapi-generator-plugin/build.gradle index 821aa991..c612eb3d 100644 --- a/openapi-generator-plugin/build.gradle +++ b/openapi-generator-plugin/build.gradle @@ -80,9 +80,9 @@ tasks.withType(Sign) { dependsOn tasks.withType(Jar) } -tasks.withType(PublishToMavenLocal) { - dependsOn tasks.withType(Sign) -} +// tasks.withType(PublishToMavenLocal) { +// dependsOn tasks.withType(Sign) +// } // Debug task to check if the service file exists task checkServiceFile { @@ -95,3 +95,16 @@ task checkServiceFile { } } } + +// Debug task to check version +task printVersion { + doLast { + println "Project version: ${project.version}" + println "Root project version: ${rootProject.version}" + println "isSnapshot property: ${project.hasProperty('isSnapshot')}" + if (project.hasProperty('isSnapshot')) { + println "isSnapshot value: ${project.isSnapshot}" + } + println "Version ends with SNAPSHOT: ${project.version.endsWith('-SNAPSHOT')}" + } +} From 39f13b0235448cd3482fdac60ecfb9fd3c04f5b3 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 14:51:19 -0500 Subject: [PATCH 24/44] refactor: streamline build and publish steps in PR snapshot workflow --- .github/workflows/pr-snapshot.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml index 9676b452..bb92062f 100644 --- a/.github/workflows/pr-snapshot.yml +++ b/.github/workflows/pr-snapshot.yml @@ -30,10 +30,7 @@ jobs: SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }} run: | # Build the plugin first - ./gradlew :openapi-generator-plugin:build - - # Publish the plugin to Maven Local so api-client-library can use it - ./gradlew :openapi-generator-plugin:publishToMavenLocal + ./gradlew -PisSnapshot=true :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal # Set up parameters based on available secrets GRADLE_PARAMS="-PisSnapshot=true" From adc62d94638ab0301056dff47858e6f70a7b8370 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 14:56:00 -0500 Subject: [PATCH 25/44] refactor: update build step to include publishing the plugin to Maven Local --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70a86f88..5cb91fff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,8 @@ jobs: with: { java-version: 17, distribution: temurin } - name: Build and Verify run: | - # Build the plugin first and check its contents - ./gradlew :openapi-generator-plugin:build + # Build and publish the plugin to Maven Local first + ./gradlew :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal - # Now run the full build + # Now run the full build (this will use the plugin from Maven Local) ./gradlew clean build From 8e669885acbcc399177f54c96fa8e8cab406c24c Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 14:57:49 -0500 Subject: [PATCH 26/44] refactor: add snapshot flag to gradle commands in build workflow --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cb91fff..4eedf7bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - name: Build and Verify run: | # Build and publish the plugin to Maven Local first - ./gradlew :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal + ./gradlew -PisSnapshot=true :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal # Now run the full build (this will use the plugin from Maven Local) - ./gradlew clean build + ./gradlew -PisSnapshot=trueclean build From 07bf267ec6e484c3e21070bf3d4b81b31830beb7 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 14:59:56 -0500 Subject: [PATCH 27/44] fix: add space in gradlew command for proper execution in build workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4eedf7bc..4fef0591 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,4 +15,4 @@ jobs: ./gradlew -PisSnapshot=true :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal # Now run the full build (this will use the plugin from Maven Local) - ./gradlew -PisSnapshot=trueclean build + ./gradlew -PisSnapshot=true clean build From f5ded0225112fc23b71a351dcf3af1b468ceb986 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 16:38:33 -0500 Subject: [PATCH 28/44] use convention plugins --- build.gradle | 75 --- buildSrc/build.gradle | 15 + .../groovy/vertex.java-conventions.gradle | 79 +++ .../groovy/vertex.openapi-generator.gradle | 44 ++ .../codegen/VertexJavaClientCodegen.java | 99 +++ .../org.openapitools.codegen.CodegenConfig | 1 + .../main/resources/vertex-java/pojo.mustache | 601 ++++++++++++++++++ 7 files changed, 839 insertions(+), 75 deletions(-) delete mode 100644 build.gradle create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/vertex.java-conventions.gradle create mode 100644 buildSrc/src/main/groovy/vertex.openapi-generator.gradle create mode 100644 buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java create mode 100644 buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig create mode 100644 buildSrc/src/main/resources/vertex-java/pojo.mustache diff --git a/build.gradle b/build.gradle deleted file mode 100644 index de406256..00000000 --- a/build.gradle +++ /dev/null @@ -1,75 +0,0 @@ -plugins { - id "idea" - id "io.github.gradle-nexus.publish-plugin" version "2.0.0" -} - -// Define version once at the root level -def projectVersion = '0.11.0' -def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() -version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion - -// Set group at the root level -group = 'com.vertexvis' - -allprojects { - group = 'com.vertexvis' - version = rootProject.version - - // Make project version accessible as a project property for buildscript blocks - ext.projectVersion = projectVersion - - repositories { - mavenLocal() // Look for local artifacts first - mavenCentral() - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") - credentials { - username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" - password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" - } - } - } -} - -subprojects { - apply plugin: 'java-library' - - java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } - } - - test { - useJUnitPlatform() - testLogging { - events "passed", "skipped", "failed" - } - } -} - -// Ensure proper build order between projects -gradle.projectsEvaluated { - // This block runs after all projects have been evaluated - project(':api-client-library').tasks.named('openApiGenerate') { - // First, make sure the plugin is built - dependsOn project(':openapi-generator-plugin').tasks.named('build') - } -} - - -nexusPublishing { - repositories { - sonatype { - nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) - snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) - username = project.hasProperty("ossrhUsername") ? project.ossrhUsername : "" - password = project.hasProperty("ossrhPassword") ? project.ossrhPassword : "" - } - } -} - -def base64Decode(prop) { - return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() -} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..d3ce4e01 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'groovy-gradle-plugin' +} + +repositories { + gradlePluginPortal() + mavenCentral() +} + +dependencies { + implementation 'org.openapitools:openapi-generator-gradle-plugin:7.14.0' + implementation 'org.openapitools:openapi-generator:7.14.0' + implementation 'io.swagger.core.v3:swagger-models:2.2.31' + implementation 'io.github.gradle-nexus:publish-plugin:2.0.0' +} diff --git a/buildSrc/src/main/groovy/vertex.java-conventions.gradle b/buildSrc/src/main/groovy/vertex.java-conventions.gradle new file mode 100644 index 00000000..ad82efb2 --- /dev/null +++ b/buildSrc/src/main/groovy/vertex.java-conventions.gradle @@ -0,0 +1,79 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'signing' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } + withJavadocJar() + withSourcesJar() +} + +test { + useJUnitPlatform() +} + +repositories { + mavenLocal() + mavenCentral() +} + +publishing { + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" + } + } + } + publications { + maven(MavenPublication) { + from components.java + pom { + url = 'https://github.com/Vertexvis/vertex-api-client-java' + licenses { + license { + name = 'MIT' + url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE' + } + } + developers { + developer { + email = 'support@vertexvis.com' + name = 'Vertex Developers' + organizationUrl = 'https://developer.vertexvis.com/' + } + } + scm { + connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' + developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' + url = 'https://github.com/Vertexvis/vertex-api-client-java' + } + } + } + } +} + +signing { + def hasSigningKey = project.hasProperty("signingKey") + def hasSigningPassword = project.hasProperty("signingPassword") + required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") } + if (hasSigningKey && hasSigningPassword) { + def base64Decode = { prop -> + return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() + } + useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) + } + sign publishing.publications.maven +} + +tasks.withType(Sign) { + dependsOn tasks.withType(GenerateModuleMetadata) + dependsOn tasks.withType(Jar) +} diff --git a/buildSrc/src/main/groovy/vertex.openapi-generator.gradle b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle new file mode 100644 index 00000000..20b0530c --- /dev/null +++ b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle @@ -0,0 +1,44 @@ +plugins { + id 'org.openapi.generator' +} + +repositories { + mavenLocal() + mavenCentral() +} + +openApiGenerate { + verbose = false + generatorName = 'vertex-java' // Use our custom generator + generateModelTests = false + generateApiTests = false + generateModelDocumentation = false + remoteInputSpec = 'https://platform.vertexvis.com/spec' + outputDir = "${buildDir}/generated/" + invokerPackage = 'com.vertexvis' + modelPackage = 'com.vertexvis.model' + apiPackage = 'com.vertexvis.api' + templateDir = "${project.rootDir}/buildSrc/src/main/resources/vertex-java" + configOptions = [ + openApiNullable: "false", + dateLibrary: "java8", + hideGenerationTimestamp: "true", + useRuntimeException: "true", + ] + additionalProperties = [ + skipValidationFor: "Part,PartData,PartDataAttributes,QueuedJobData,QueuedJob,QueuedJobDataAttributes" // Comma-separated list of models to skip validation for + ] + ignoreFileOverride = "${projectDir}/.openapi-generator-ignore" +} + +// Ensure generated sources are included in the source sets +sourceSets { + main { + java { + srcDir "${buildDir}/generated/src/main/java" + } + } +} + +// Make sure compilation depends on code generation +compileJava.dependsOn tasks.openApiGenerate diff --git a/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java b/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java new file mode 100644 index 00000000..b7ee2605 --- /dev/null +++ b/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java @@ -0,0 +1,99 @@ +package com.vertexvis.codegen; + +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.languages.JavaClientCodegen; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.model.ModelsMap; +import org.openapitools.codegen.model.ModelMap; +import io.swagger.v3.oas.models.media.Schema; +import java.util.*; + +/** + * Custom Java client codegen that supports conditional validation skipping. + */ +public class VertexJavaClientCodegen extends JavaClientCodegen { + private static final Logger LOGGER = LoggerFactory.getLogger(VertexJavaClientCodegen.class); + + /** Configuration option key for specifying models to skip validation for. */ + public static final String SKIP_VALIDATION_FOR = "skipValidationFor"; + /** Vendor extension key used to mark models that should skip validation. */ + public static final String X_SKIP_VALIDATION = "x-skip-validation"; + + private Set skipValidationModels = new HashSet<>(); + + /** + * Default constructor that sets up the custom CLI options. + */ + public VertexJavaClientCodegen() { + super(); + + // Add custom CLI option + cliOptions.add(org.openapitools.codegen.CliOption.newString(SKIP_VALIDATION_FOR, + "Comma-separated list of model class names to skip validation for (e.g., Scene,SceneMetadata)")); + } + + @Override + public String getName() { + return "vertex-java"; + } + + @Override + public String getHelp() { + return "Generates a Vertex-customized Java client library."; + } + + @Override + public void processOpts() { + super.processOpts(); + + // Parse the skipValidationFor option + if (additionalProperties.containsKey(SKIP_VALIDATION_FOR)) { + String skipValidationForValue = (String) additionalProperties.get(SKIP_VALIDATION_FOR); + if (skipValidationForValue != null && !skipValidationForValue.trim().isEmpty()) { + String[] modelNames = skipValidationForValue.split(","); + for (String modelName : modelNames) { + skipValidationModels.add(modelName.trim()); + } + LOGGER.info("Models to skip validation: {}", skipValidationModels); + } + } + } + + @Override + public CodegenModel fromModel(String name, Schema schema) { + CodegenModel model = super.fromModel(name, schema); + + // Check if this model should skip validation + if (skipValidationModels.contains(model.classname)) { + LOGGER.info("Adding skip validation extension for model: {}", model.classname); + model.vendorExtensions.put(X_SKIP_VALIDATION, true); + } + + return model; + } + + @Override + public ModelsMap postProcessModels(ModelsMap objs) { + var result = super.postProcessModels(objs); + + // Additional processing can be done here if needed + @SuppressWarnings("unchecked") + List models = (List) objs.get("models"); + + if (models != null) { + for (ModelMap modelMap : models) { + CodegenModel model = modelMap.getModel(); + + if (model.vendorExtensions.containsKey(X_SKIP_VALIDATION)) { + LOGGER.debug("Model {} will skip validation", model.classname); + } + } + } + + return result; + } +} diff --git a/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig new file mode 100644 index 00000000..953e26e4 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -0,0 +1 @@ +com.vertexvis.codegen.VertexJavaClientCodegen diff --git a/buildSrc/src/main/resources/vertex-java/pojo.mustache b/buildSrc/src/main/resources/vertex-java/pojo.mustache new file mode 100644 index 00000000..e1f16c3b --- /dev/null +++ b/buildSrc/src/main/resources/vertex-java/pojo.mustache @@ -0,0 +1,601 @@ +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import {{invokerPackage}}.JSON; + +/** + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */{{#isDeprecated}} +@Deprecated{{/isDeprecated}} +{{#swagger1AnnotationLibrary}} +{{#description}} +@ApiModel(description = "{{{.}}}") +{{/description}} +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} +public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{ +{{#serializableModel}} + private static final long serialVersionUID = 1L; + +{{/serializableModel}} + {{#vars}} + {{#isEnum}} + {{^isContainer}} +{{>modelInnerEnum}} + + {{/isContainer}} + {{#isContainer}} + {{#mostInnerItems}} +{{>modelInnerEnum}} + + {{/mostInnerItems}} + {{/isContainer}} + {{/isEnum}} + public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; + {{#withXml}} + @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{#isXmlWrapped}} + @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) + {{/isXmlWrapped}} + {{/withXml}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{#vendorExtensions.x-field-extra-annotation}} + {{{vendorExtensions.x-field-extra-annotation}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{>nullable_var_annotations}}{{! prevent indent}} + {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + + {{/vars}} + public {{classname}}() { + {{#parent}} + {{#parcelableModel}} + super(); + {{/parcelableModel}} + {{/parent}} + {{#discriminator}} + {{#discriminator.isEnum}} +{{#readWriteVars}}{{#isDiscriminator}}{{#defaultValue}} + this.{{name}} = {{defaultValue}}; +{{/defaultValue}}{{/isDiscriminator}}{{/readWriteVars}} + {{/discriminator.isEnum}} + {{^discriminator.isEnum}} + this.{{{discriminatorName}}} = this.getClass().getSimpleName(); + {{/discriminator.isEnum}} + {{/discriminator}} + } + {{#vendorExtensions.x-has-readonly-properties}} + {{^withXml}} + + public {{classname}}( + {{#readOnlyVars}} + {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + this(); + {{#readOnlyVars}} + this.{{name}} = {{name}}; + {{/readOnlyVars}} + } + {{/withXml}} + {{/vendorExtensions.x-has-readonly-properties}} + {{#vars}} + + {{^isReadOnly}} + {{#deprecated}} + @Deprecated + {{/deprecated}} + public {{classname}} {{name}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + return this; + } + {{#isArray}} + + public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + } + this.{{name}}.add({{name}}Item); + return this; + } + {{/isArray}} + {{#isMap}} + + public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + } + this.{{name}}.put(key, {{name}}Item); + return this; + } + {{/isMap}} + + {{/isReadOnly}} + /** + {{#description}} + * {{.}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{.}} + {{/minimum}} + {{#maximum}} + * maximum: {{.}} + {{/maximum}} + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ +{{#deprecated}} + @Deprecated +{{/deprecated}} + {{>nullable_var_annotations}}{{! prevent indent}} +{{#useBeanValidation}} +{{>beanValidation}} + +{{/useBeanValidation}} +{{#swagger1AnnotationLibrary}} + @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} + @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}") +{{/swagger2AnnotationLibrary}} +{{#vendorExtensions.x-extra-annotation}} + {{{vendorExtensions.x-extra-annotation}}} +{{/vendorExtensions.x-extra-annotation}} + public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + } + + {{^isReadOnly}} +{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#deprecated}} @Deprecated +{{/deprecated}} public void {{setter}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + } + {{/isReadOnly}} + + {{/vars}} +{{>libraries/okhttp-gson/additional_properties}} + + + @Override + public boolean equals(Object o) { + {{#useReflectionEqualsHashCode}} + return EqualsBuilder.reflectionEquals(this, o, false, null, true); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{^-last}} && + {{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}&& + Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/isAdditionalPropertiesTrue}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static boolean equalsNullable(JsonNullable a, JsonNullable b) { + return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @Override + public int hashCode() { + {{#useReflectionEqualsHashCode}} + return HashCodeBuilder.reflectionHashCode(this); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}}); + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static int hashCodeNullable(JsonNullable a) { + if (a == null) { + return 1; + } + return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}} + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + {{/parent}} + {{#vars}} + sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); + {{/vars}} +{{#isAdditionalPropertiesTrue}} + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); +{{/isAdditionalPropertiesTrue}} + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +{{#parcelableModel}} + + public void writeToParcel(Parcel out, int flags) { +{{#model}} +{{#isArray}} + out.writeList(this); +{{/isArray}} +{{^isArray}} +{{#parent}} + super.writeToParcel(out, flags); +{{/parent}} +{{#vars}} + out.writeValue({{name}}); +{{/vars}} +{{/isArray}} +{{/model}} + } + + {{classname}}(Parcel in) { +{{#isArray}} + in.readTypedList(this, {{arrayModelType}}.CREATOR); +{{/isArray}} +{{^isArray}} +{{#parent}} + super(in); +{{/parent}} +{{#vars}} +{{#isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); +{{/isPrimitiveType}} +{{^isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); +{{/isPrimitiveType}} +{{/vars}} +{{/isArray}} + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public {{classname}} createFromParcel(Parcel in) { +{{#model}} +{{#isArray}} + {{classname}} result = new {{classname}}(); + result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); + return result; +{{/isArray}} +{{^isArray}} + return new {{classname}}(in); +{{/isArray}} +{{/model}} + } + public {{classname}}[] newArray(int size) { + return new {{classname}}[size]; + } + }; +{{/parcelableModel}} + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + {{#hasVars}} + openapiFields = new HashSet(Arrays.asList({{#allVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/allVars}})); + {{/hasVars}} + {{^hasVars}} + openapiFields = new HashSet(0); + {{/hasVars}} + + // a set of required properties/fields (JSON key names) + {{#hasRequired}} + openapiRequiredFields = new HashSet(Arrays.asList({{#requiredVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/requiredVars}})); + {{/hasRequired}} + {{^hasRequired}} + openapiRequiredFields = new HashSet(0); + {{/hasRequired}} + } + + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to {{classname}} + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + if (jsonElement == null) { + if (!{{classname}}.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null + throw new IllegalArgumentException(String.format("The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString())); + } + } + {{^hasChildren}} + {{^isAdditionalPropertiesTrue}} + + Set> entries = jsonElement.getAsJsonObject().entrySet(); + // check to see if the JSON string contains additional fields + for (Map.Entry entry : entries) { + if (!{{classname}}.openapiFields.contains(entry.getKey())) { + throw new IllegalArgumentException(String.format("The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", entry.getKey(), jsonElement.toString())); + } + } + {{/isAdditionalPropertiesTrue}} + {{#requiredVars}} + {{#-first}} + + // check to make sure all required properties/fields are present in the JSON string + for (String requiredField : {{classname}}.openapiRequiredFields) { + if (jsonElement.getAsJsonObject().get(requiredField) == null) { + throw new IllegalArgumentException(String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())); + } + } + {{/-first}} + {{/requiredVars}} + {{/hasChildren}} + {{^discriminator}} + {{#hasVars}} + JsonObject jsonObj = jsonElement.getAsJsonObject(); + {{/hasVars}} + {{#vars}} + {{#isArray}} + {{#items.isModel}} + {{#required}} + // ensure the json data is an array + if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + + JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}"); + // validate the required field `{{{baseName}}}` (array) + for (int i = 0; i < jsonArray{{name}}.size(); i++) { + {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i)); + }; + {{/required}} + {{^required}} + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}"); + if (jsonArray{{name}} != null) { + // ensure the json data is an array + if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + + // validate the optional field `{{{baseName}}}` (array) + for (int i = 0; i < jsonArray{{name}}.size(); i++) { + {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i)); + }; + } + } + {{/required}} + {{/items.isModel}} + {{^items.isModel}} + {{^required}} + // ensure the optional json data is an array if present + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull() && !jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + {{/required}} + {{#required}} + // ensure the required json array is present + if (jsonObj.get("{{{baseName}}}") == null) { + throw new IllegalArgumentException("Expected the field `linkedContent` to be an array in the JSON string but got `null`"); + } else if (!jsonObj.get("{{{baseName}}}").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + {{/required}} + {{/items.isModel}} + {{/isArray}} + {{^isContainer}} + {{#isString}} + if ({{#notRequiredOrIsNullable}}(jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) && {{/notRequiredOrIsNullable}}!jsonObj.get("{{{baseName}}}").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString())); + } + {{/isString}} + {{#isModel}} + {{#required}} + // validate the required field `{{{baseName}}}` + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{/required}} + {{^required}} + // validate the optional field `{{{baseName}}}` + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + } + {{/required}} + {{/isModel}} + {{#isEnum}} + {{#required}} + // validate the required field `{{{baseName}}}` + {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{/required}} + {{^required}} + // validate the optional field `{{{baseName}}}` + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + } + {{/required}} + {{/isEnum}} + {{#isEnumRef}} + {{#required}} + // validate the required field `{{{baseName}}}` + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + {{/required}} + {{^required}} + // validate the optional field `{{{baseName}}}` + if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) { + {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}")); + } + {{/required}} + {{/isEnumRef}} + {{/isContainer}} + {{/vars}} + {{/discriminator}} + {{#hasChildren}} + {{#discriminator}} + + String discriminatorValue = jsonElement.getAsJsonObject().get("{{{propertyBaseName}}}").getAsString(); + switch (discriminatorValue) { + {{#mappedModels}} + case "{{mappingName}}": + {{modelName}}.validateJsonElement(jsonElement); + break; + {{/mappedModels}} + default: + throw new IllegalArgumentException(String.format("The value of the `{{{propertyBaseName}}}` field `%s` does not match any key defined in the discriminator's mapping.", discriminatorValue)); + } + {{/discriminator}} + {{/hasChildren}} + } + +{{^hasChildren}} + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!{{classname}}.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes '{{classname}}' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter<{{classname}}> thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get({{classname}}.class)); + + return (TypeAdapter) new TypeAdapter<{{classname}}>() { + @Override + public void write(JsonWriter out, {{classname}} value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + {{#isAdditionalPropertiesTrue}} + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + JsonElement jsonElement = gson.toJsonTree(entry.getValue()); + if (jsonElement.isJsonArray()) { + obj.add(entry.getKey(), jsonElement.getAsJsonArray()); + } else { + obj.add(entry.getKey(), jsonElement.getAsJsonObject()); + } + } + } + } + {{/isAdditionalPropertiesTrue}} + elementAdapter.write(out, obj); + } + + @Override + public {{classname}} read(JsonReader in) throws IOException { + JsonElement jsonElement = elementAdapter.read(in); +{{^vendorExtensions.x-skip-validation}} + validateJsonElement(jsonElement); +{{/vendorExtensions.x-skip-validation}} +{{#vendorExtensions.x-skip-validation}} + // vertex:TODO: Skipping validation during deserialization of the JSON element based on configuration + // validateJsonElement(jsonElement); +{{/vendorExtensions.x-skip-validation}} + {{#isAdditionalPropertiesTrue}} + JsonObject jsonObj = jsonElement.getAsJsonObject(); + // store additional fields in the deserialized instance + {{classname}} instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format("The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + {{/isAdditionalPropertiesTrue}} + {{^isAdditionalPropertiesTrue}} + return thisAdapter.fromJsonTree(jsonElement); + {{/isAdditionalPropertiesTrue}} + } + + }.nullSafe(); + } + } +{{/hasChildren}} + + /** + * Create an instance of {{classname}} given an JSON string + * + * @param jsonString JSON string + * @return An instance of {{classname}} + * @throws IOException if the JSON string is invalid with respect to {{classname}} + */ + public static {{{classname}}} fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); + } + + /** + * Convert an instance of {{classname}} to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} \ No newline at end of file From ed0c7bb8cca632fe4b5d42197faa04cb17a806e7 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 16:40:18 -0500 Subject: [PATCH 29/44] refactor: streamline build.gradle files by removing unnecessary configurations and adding conventions --- api-client-library/build.gradle | 132 ++------------------------ examples/build.gradle | 1 + openapi-generator-plugin/build.gradle | 5 + 3 files changed, 16 insertions(+), 122 deletions(-) diff --git a/api-client-library/build.gradle b/api-client-library/build.gradle index e5e2eebf..ae7d3b12 100644 --- a/api-client-library/build.gradle +++ b/api-client-library/build.gradle @@ -1,17 +1,6 @@ -buildscript{ - repositories { - mavenLocal() - mavenCentral() - } - dependencies { - classpath "com.vertexvis:openapi-generator-plugin:${rootProject.version}" - } -} plugins { - id 'java-library' - id 'maven-publish' - id 'signing' - id 'org.openapi.generator' version '7.14.0' + id 'vertex.java-conventions' + id 'vertex.openapi-generator' } description = 'Vertex API Client Library for Java' @@ -33,118 +22,26 @@ dependencies { testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") } -openApiGenerate { - verbose = false - generatorName = 'vertex-java' // Use our custom generator - generateModelTests = false - generateApiTests = false - generateModelDocumentation = false - remoteInputSpec = 'https://platform.vertexvis.com/spec' - outputDir = "${buildDir}/generated/" - invokerPackage = 'com.vertexvis' - modelPackage = 'com.vertexvis.model' - apiPackage = 'com.vertexvis.api' - templateDir = "${project(':openapi-generator-plugin').projectDir}/src/main/resources/vertex-java" - configOptions = [ - openApiNullable: "false", - dateLibrary: "java8", - hideGenerationTimestamp: "true", - useRuntimeException: "true", - ] - additionalProperties = [ - skipValidationFor: "Part,PartData,PartDataAttributes,QueuedJobData,QueuedJob,QueuedJobDataAttributes" // Comma-separated list of models to skip validation for - ] - ignoreFileOverride = "${projectDir}/.openapi-generator-ignore" -} - -sourceSets { - main { - java { - srcDirs += [ - "${buildDir}/generated/src/main/java" - ] - } - } -} -tasks.named("openApiGenerate").configure { - dependsOn(":openapi-generator-plugin:build") -} - -compileJava.dependsOn tasks.openApiGenerate -compileTestJava.dependsOn tasks.openApiGenerate - -// Ensure our custom generator plugin is built before we generate -tasks.openApiGenerate.dependsOn ':openapi-generator-plugin:build' - -java { - withJavadocJar() - withSourcesJar() -} - -tasks.named('sourcesJar') { - dependsOn tasks.openApiGenerate - from sourceSets.main.allJava - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -jar { - from sourceSets.main.allSource - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - +// Override publication configuration for this specific library publishing { - repositories { - maven { - name = 'GitHubPackages' - url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java") - credentials { - username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: "" - password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: "" - } - } - } publications { maven(MavenPublication) { artifactId = 'api-client-java' - from components.java pom { name = 'com.vertexvis:api-client-java' description = 'The Vertex REST API client for Java.' - url = 'https://github.com/Vertexvis/vertex-api-client-java' - licenses { - license { - name = 'MIT' - url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE' - } - } - developers { - developer { - email = 'support@vertexvis.com' - name = 'Vertex Developers' - organizationUrl = 'https://developer.vertexvis.com/' - } - } - scm { - connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' - developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git' - url = 'https://github.com/Vertexvis/vertex-api-client-java' - } } } } } -signing { - def hasSigningKey = project.hasProperty("signingKey") - def hasSigningPassword = project.hasProperty("signingPassword") - required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") } - if (hasSigningKey && hasSigningPassword) { - def base64Decode = { prop -> - return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() - } - useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword")) - } - sign publishing.publications.maven +// Ensure proper task dependencies for generated sources +tasks.named('sourcesJar') { + dependsOn tasks.openApiGenerate +} + +tasks.named('javadocJar') { + dependsOn tasks.openApiGenerate } javadoc { @@ -154,12 +51,3 @@ javadoc { } dependsOn tasks.openApiGenerate } - -tasks.withType(Sign) { - dependsOn tasks.withType(GenerateModuleMetadata) - dependsOn tasks.withType(Jar) -} - -// tasks.withType(PublishToMavenLocal) { -// dependsOn tasks.withType(Sign) -// } diff --git a/examples/build.gradle b/examples/build.gradle index 851083a5..8032d755 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'application' + id 'vertex.java-conventions' } description = 'Example applications using the Vertex API Client Library' diff --git a/openapi-generator-plugin/build.gradle b/openapi-generator-plugin/build.gradle index c612eb3d..ca9b6e61 100644 --- a/openapi-generator-plugin/build.gradle +++ b/openapi-generator-plugin/build.gradle @@ -6,6 +6,11 @@ plugins { description = 'Custom OpenAPI Generator Plugin for Vertex API Client' +repositories { + mavenLocal() + mavenCentral() +} + dependencies { implementation 'org.openapitools:openapi-generator:7.14.0' implementation 'org.openapitools:openapi-generator-gradle-plugin:7.14.0' From 17f4145b736d1121534ee7890e79c2591f395a43 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 16:40:34 -0500 Subject: [PATCH 30/44] refactor: update build workflow to remove plugin publishing step and streamline the build process --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fef0591..eb39f379 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,5 @@ jobs: with: { java-version: 17, distribution: temurin } - name: Build and Verify run: | - # Build and publish the plugin to Maven Local first - ./gradlew -PisSnapshot=true :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal - - # Now run the full build (this will use the plugin from Maven Local) + # With convention plugins, everything is self-contained ./gradlew -PisSnapshot=true clean build From cc3a474d58a8f39f5d9a558e14014c91e29c8204 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 16:56:08 -0500 Subject: [PATCH 31/44] feat: add initial build.gradle configuration for project setup and nexus publishing --- build.gradle | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 build.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..5b81c5b3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "idea" + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" +} + +def projectVersion = '0.11.0' +def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() +version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion + +allprojects { + group = 'com.vertexvis' + version = rootProject.version + + // Make project version accessible as a project property for buildscript blocks + ext.projectVersion = projectVersion +} + +nexusPublishing { + repositories { + sonatype { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + username = project.findProperty("ossrhUsername") ?: System.getenv("OSSRH_USERNAME") + password = project.findProperty("ossrhPassword") ?: System.getenv("OSSRH_PASSWORD") + } + } +} From f6a9817a50bb1df79159d1b926fe69790ddac11f Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 16:58:39 -0500 Subject: [PATCH 32/44] fix: remove version specification for nexus publish plugin in build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b81c5b3..8e7b9ac5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "idea" - id "io.github.gradle-nexus.publish-plugin" version "2.0.0" + id "io.github.gradle-nexus.publish-plugin" } def projectVersion = '0.11.0' From 251708e4665d3c59a49dadcbc35d64107c459e4b Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 17:02:56 -0500 Subject: [PATCH 33/44] fix: update nexus repository URLs in build.gradle for staging environment --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8e7b9ac5..daa9f223 100644 --- a/build.gradle +++ b/build.gradle @@ -18,8 +18,8 @@ allprojects { nexusPublishing { repositories { sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) username = project.findProperty("ossrhUsername") ?: System.getenv("OSSRH_USERNAME") password = project.findProperty("ossrhPassword") ?: System.getenv("OSSRH_PASSWORD") } From f1ca7ea98507a6ce2a5d838141f8249635dc1923 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 17:26:31 -0500 Subject: [PATCH 34/44] feat: add version check workflow for pull requests to ensure version increments --- .github/workflows/version-check.yml | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/version-check.yml diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml new file mode 100644 index 00000000..b0ae14bd --- /dev/null +++ b/.github/workflows/version-check.yml @@ -0,0 +1,80 @@ +name: Version Check + +on: + pull_request: + branches: [ main ] + +jobs: + version-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history to compare branches + + - uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Check Version Increment + run: | + # Extract current version from build.gradle + CURRENT_VERSION=$(grep "def projectVersion" build.gradle | sed "s/def projectVersion = '//" | sed "s/'//") + echo "Current version: $CURRENT_VERSION" + + # Get the target branch version + git checkout origin/${{ github.base_ref }} + TARGET_VERSION=$(grep "def projectVersion" build.gradle | sed "s/def projectVersion = '//" | sed "s/'//") + echo "Target branch version: $TARGET_VERSION" + + # Switch back to PR branch + git checkout ${{ github.head_ref }} + + # Function to compare semantic versions + version_compare() { + local version1=$1 + local version2=$2 + + # Split versions into arrays + IFS='.' read -ra V1 <<< "$version1" + IFS='.' read -ra V2 <<< "$version2" + + # Compare major version + if [ "${V1[0]}" -gt "${V2[0]}" ]; then + return 0 # version1 > version2 + elif [ "${V1[0]}" -lt "${V2[0]}" ]; then + return 1 # version1 < version2 + fi + + # Compare minor version + if [ "${V1[1]}" -gt "${V2[1]}" ]; then + return 0 + elif [ "${V1[1]}" -lt "${V2[1]}" ]; then + return 1 + fi + + # Compare patch version + if [ "${V1[2]}" -gt "${V2[2]}" ]; then + return 0 + elif [ "${V1[2]}" -lt "${V2[2]}" ]; then + return 1 + fi + + # Versions are equal + return 2 + } + + # Compare versions + if version_compare "$CURRENT_VERSION" "$TARGET_VERSION"; then + echo "✅ Version increment detected: $TARGET_VERSION → $CURRENT_VERSION" + exit 0 + elif [ $? -eq 2 ]; then + echo "❌ Version not incremented: $CURRENT_VERSION is the same as $TARGET_VERSION" + echo "Please increment the version in build.gradle" + exit 1 + else + echo "❌ Version decreased: $TARGET_VERSION → $CURRENT_VERSION" + echo "Version should only increase, not decrease" + exit 1 + fi From 0cf4eb9d5eb1112bfc6b1eee3dadbeb252eacc56 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 17:27:35 -0500 Subject: [PATCH 35/44] fix: update project version to 0.11.1 in build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index daa9f223..324d668f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id "io.github.gradle-nexus.publish-plugin" } -def projectVersion = '0.11.0' +def projectVersion = '0.11.1' def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion From e663314ccf66881b65857b27674930ec31e545e3 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 17:37:24 -0500 Subject: [PATCH 36/44] fix: improve version extraction logic in version-check workflow --- .github/workflows/version-check.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index b0ae14bd..3a18e445 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -20,17 +20,28 @@ jobs: - name: Check Version Increment run: | # Extract current version from build.gradle - CURRENT_VERSION=$(grep "def projectVersion" build.gradle | sed "s/def projectVersion = '//" | sed "s/'//") + CURRENT_VERSION=$(grep "def projectVersion" build.gradle | sed -E "s/.*def projectVersion = ['\"]([^'\"]*)['\"].*/\1/") echo "Current version: $CURRENT_VERSION" # Get the target branch version git checkout origin/${{ github.base_ref }} - TARGET_VERSION=$(grep "def projectVersion" build.gradle | sed "s/def projectVersion = '//" | sed "s/'//") + TARGET_VERSION=$(grep "def projectVersion" build.gradle | sed -E "s/.*def projectVersion = ['\"]([^'\"]*)['\"].*/\1/") echo "Target branch version: $TARGET_VERSION" # Switch back to PR branch git checkout ${{ github.head_ref }} + # Validate that we extracted versions correctly + if [ -z "$CURRENT_VERSION" ]; then + echo "❌ Failed to extract current version from build.gradle" + exit 1 + fi + + if [ -z "$TARGET_VERSION" ]; then + echo "❌ Failed to extract target version from build.gradle" + exit 1 + fi + # Function to compare semantic versions version_compare() { local version1=$1 @@ -40,6 +51,10 @@ jobs: IFS='.' read -ra V1 <<< "$version1" IFS='.' read -ra V2 <<< "$version2" + # Ensure we have 3 parts for each version + while [ ${#V1[@]} -lt 3 ]; do V1+=(0); done + while [ ${#V2[@]} -lt 3 ]; do V2+=(0); done + # Compare major version if [ "${V1[0]}" -gt "${V2[0]}" ]; then return 0 # version1 > version2 From d96ce134e1410c038078b6a6536ef5e2a8424337 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:03:54 -0500 Subject: [PATCH 37/44] fix: streamline version extraction and comparison logic in version-check workflow --- .github/workflows/version-check.yml | 48 +++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 3a18e445..9698c6a7 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -23,38 +23,54 @@ jobs: CURRENT_VERSION=$(grep "def projectVersion" build.gradle | sed -E "s/.*def projectVersion = ['\"]([^'\"]*)['\"].*/\1/") echo "Current version: $CURRENT_VERSION" - # Get the target branch version - git checkout origin/${{ github.base_ref }} - TARGET_VERSION=$(grep "def projectVersion" build.gradle | sed -E "s/.*def projectVersion = ['\"]([^'\"]*)['\"].*/\1/") - echo "Target branch version: $TARGET_VERSION" - - # Switch back to PR branch - git checkout ${{ github.head_ref }} - - # Validate that we extracted versions correctly + # Validate that we extracted current version correctly if [ -z "$CURRENT_VERSION" ]; then echo "❌ Failed to extract current version from build.gradle" exit 1 fi - if [ -z "$TARGET_VERSION" ]; then - echo "❌ Failed to extract target version from build.gradle" - exit 1 + # Get the latest Git tag from the target branch + git fetch origin ${{ github.base_ref }} + git checkout origin/${{ github.base_ref }} + + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$LATEST_TAG" ]; then + echo "No existing tags found. Assuming this is the first release." + LATEST_TAG="0.0.0" fi + # Remove 'v' prefix if present + TARGET_VERSION=$(echo "$LATEST_TAG" | sed 's/^v//') + echo "Latest tag version: $TARGET_VERSION" + + # Switch back to PR branch + git checkout ${{ github.head_ref }} + + # Function to normalize version (ensure 3 parts) + normalize_version() { + local version=$1 + IFS='.' read -ra V <<< "$version" + + # Ensure we have 3 parts + while [ ${#V[@]} -lt 3 ]; do V+=(0); done + + echo "${V[0]}.${V[1]}.${V[2]}" + } + # Function to compare semantic versions version_compare() { local version1=$1 local version2=$2 + # Normalize both versions + version1=$(normalize_version "$version1") + version2=$(normalize_version "$version2") + # Split versions into arrays IFS='.' read -ra V1 <<< "$version1" IFS='.' read -ra V2 <<< "$version2" - # Ensure we have 3 parts for each version - while [ ${#V1[@]} -lt 3 ]; do V1+=(0); done - while [ ${#V2[@]} -lt 3 ]; do V2+=(0); done - # Compare major version if [ "${V1[0]}" -gt "${V2[0]}" ]; then return 0 # version1 > version2 From 2cd7820ce81faaac730016a1bb792f2477e382da Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:05:03 -0500 Subject: [PATCH 38/44] fix: revert project version to 0.11.0 in build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 324d668f..daa9f223 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id "io.github.gradle-nexus.publish-plugin" } -def projectVersion = '0.11.1' +def projectVersion = '0.11.0' def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion From 4d67dc3e9d1bff97f74873ac20eb4feb5cbdb48b Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:08:55 -0500 Subject: [PATCH 39/44] fix: update build workflow trigger to ignore pushes to main branch --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb39f379..448e307e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,8 @@ name: build -on: [pull_request] +on: + push: + branches-ignore: [main] jobs: build: From 678d1cd5ca633ac05a256657dec3e23cef16a169 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:12:03 -0500 Subject: [PATCH 40/44] fix: update version extraction logic in version-lib.sh to match new project version format --- .github/workflows/publish.yml | 2 +- scripts/version-lib.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f320324e..00ca3981 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -38,5 +38,5 @@ jobs: -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \ :api-client-library:publishToSonatype \ - :api-client-library:closeAndReleaseSonatypeStagingRepository + closeAndReleaseSonatypeStagingRepository ./scripts/publish.sh diff --git a/scripts/version-lib.sh b/scripts/version-lib.sh index 9c38564f..b688f6c9 100644 --- a/scripts/version-lib.sh +++ b/scripts/version-lib.sh @@ -46,8 +46,8 @@ _die() { # # Returns version. _get_version() { - local prefix="version = " - grep "$prefix" build.gradle | tr -d "$prefix" | tr -d "'" + local prefix="def projectVersion = " + grep "$prefix" build.gradle | sed "s/$prefix//" | tr -d "'" } # Internal: Bump API client version. From e73f633235f191c729547e23dca3c7cc56ead062 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:20:06 -0500 Subject: [PATCH 41/44] fix: update version assignment syntax in bump-version.sh to match project conventions --- scripts/bump-version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 3cebd15f..73a59a01 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -17,8 +17,8 @@ main() { new=$(_bump_version "$old" "$@") echo "Updating version from $old to $new" - sed -i "s|version = '$old'|version = '$new'|" build.gradle - sed -i "s|$old|$new|" README.md + sed -i '' "s|def projectVersion = '$old'|def projectVersion = '$new'|" build.gradle + sed -i '' "s|$old|$new|" README.md } main "$@" From 165fa12758236006beae4492f40bd1446bf7da36 Mon Sep 17 00:00:00 2001 From: gbiv Date: Wed, 23 Jul 2025 18:21:25 -0500 Subject: [PATCH 42/44] fix: update version to 0.11.0 in README.md for Maven, Gradle, and Sbt dependencies --- README.md | 84 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 45801d1d..a6812836 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects. com.vertexvis api-client-java - 0.10.0 + 0.11.0 compile ``` @@ -25,13 +25,13 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects. ### Gradle ```groovy -compile "com.vertexvis:api-client-java:0.10.0" +compile "com.vertexvis:api-client-java:0.11.0" ``` ### Sbt ```sbt -libraryDependencies += "com.vertexvis" % "api-client-java" % "0.10.0" +libraryDependencies += "com.vertexvis" % "api-client-java" % "0.11.0" ``` ### Others @@ -44,7 +44,7 @@ mvn clean package Then manually install the following JARs. -- `target/api-client-java-0.10.0.jar` +- `target/api-client-java-0.11.0.jar` - `target/lib/*.jar` ## Usage @@ -63,37 +63,64 @@ Then, check out our [sample applications](./src/main/java/com/vertexvis/example) ## Local Development -This project uses a multi-module Gradle structure. For detailed information about the modules and their purposes, refer to the [Multi-Module README](./MULTI_MODULE_README.md). +This project uses a multi-module Gradle structure with convention plugins. For detailed information about the modules and their purposes, refer to the [Multi-Module README](./MULTI_MODULE_README.md). -### Build Order +### Quick Start -1. Build the OpenAPI Generator Plugin: -```bash -./gradlew :openapi-generator-plugin:build -./gradlew :openapi-generator-plugin:publishToMavenLocal -``` +The project now uses convention plugins in `buildSrc/` which makes the build completely self-contained: -2. Generate the API Client: ```bash -./gradlew :api-client-library:openApiGenerate -``` - -3. Build the API Client Library: -```bash -./gradlew :api-client-library:build -``` +# Build everything (no separate plugin publishing needed) +./gradlew build -4. Run Example Applications: -```bash +# Run example applications ./gradlew :examples:run ./gradlew :examples:listExamples ``` -### Building the Entire Project +### Development Workflow -To build all modules: -```bash -./gradlew build +1. **Make changes** to the API client or custom generator +2. **Build and test** with `./gradlew build` +3. **Test locally** with `./gradlew :api-client-library:publishToMavenLocal` + +### Using Snapshot Versions + +To consume published snapshot versions in other projects, add the snapshot repository to your build configuration: + +#### Maven + +```xml + + + central-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + true + + + + + + com.vertexvis + api-client-java + 0.11.0-SNAPSHOT + +``` + +#### Gradle + +```groovy +repositories { + mavenCentral() + maven { + url 'https://central.sonatype.com/repository/maven-snapshots/' + } +} + +dependencies { + implementation 'com.vertexvis:api-client-java:0.11.0-SNAPSHOT' +} ``` ### Versioning @@ -105,12 +132,9 @@ To bump the version of all modules: ### Publishing -To publish to Maven Local: +To publish to Maven Local for testing: ```bash ./gradlew :api-client-library:publishToMavenLocal ``` -To publish to Maven Central: -```bash -./gradlew publish -``` +Snapshots are automatically published to Maven Central on pushes to the `main` branch. Releases are created when a new version tag is pushed. From cddf38e53fe402d6f524365562f7b859c35a949e Mon Sep 17 00:00:00 2001 From: gbiv Date: Thu, 24 Jul 2025 14:03:28 -0500 Subject: [PATCH 43/44] portable sed in bump version --- scripts/bump-version.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 73a59a01..49420d0b 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -17,8 +17,9 @@ main() { new=$(_bump_version "$old" "$@") echo "Updating version from $old to $new" - sed -i '' "s|def projectVersion = '$old'|def projectVersion = '$new'|" build.gradle - sed -i '' "s|$old|$new|" README.md + # Use portable sed approach that works on both macOS and Linux + sed "s|def projectVersion = '$old'|def projectVersion = '$new'|" build.gradle > build.gradle.tmp && mv build.gradle.tmp build.gradle + sed "s|$old|$new|g" README.md > README.md.tmp && mv README.md.tmp README.md } main "$@" From 449b656ed2545763f545628301ddb518c2edbd11 Mon Sep 17 00:00:00 2001 From: gbiv Date: Thu, 24 Jul 2025 14:29:41 -0500 Subject: [PATCH 44/44] update readme --- MULTI_MODULE_README.md | 112 +++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 72 deletions(-) diff --git a/MULTI_MODULE_README.md b/MULTI_MODULE_README.md index 8138fd60..b965b1a1 100644 --- a/MULTI_MODULE_README.md +++ b/MULTI_MODULE_README.md @@ -1,54 +1,26 @@ -# Vertex API Client Java - Multi-Module Project +# Vertex API Client Java - Project Architecture -This project has been restructured as a multi-module Gradle project with the following modules: +This project uses a modern Gradle multi-module structure with convention plugins for clean, maintainable builds. -## Modules +## Project Structure -### 1. `openapi-generator-plugin` -Custom Gradle plugin for generating Vertex API client code using OpenAPI Generator. +### Core Modules -**Location**: `openapi-generator-plugin/` - -**Purpose**: -- Contains custom OpenAPI code generation logic -- Provides a reusable Gradle plugin for generating the API client -- Includes any custom templates or generators specific to Vertex API - -**Build**: -```bash -./gradlew :openapi-generator-plugin:build -./gradlew :openapi-generator-plugin:publishToMavenLocal -``` - -### 2. `api-client-library` +#### `api-client-library/` The main API client library generated from the OpenAPI specification. -**Location**: `api-client-library/` - **Purpose**: - Contains the generated API client code -- Includes authentication utilities -- Provides the core SDK functionality - -**Build**: -```bash -./gradlew :api-client-library:build -``` +- Includes authentication utilities and models +- Provides the core SDK functionality that gets published to Maven Central -**Generate API Client**: -```bash -./gradlew :api-client-library:openApiGenerate -``` - -### 3. `examples` +#### `examples/` Example applications demonstrating how to use the API client library. -**Location**: `examples/` - **Purpose**: -- Contains example code showing API usage +- Contains example code showing API usage patterns - Includes command-line utilities for common operations -- Demonstrates best practices for using the API client +- Demonstrates best practices for authentication and API calls **Run Examples**: ```bash @@ -57,50 +29,46 @@ Example applications demonstrating how to use the API client library. ./gradlew :examples:listExamples ``` -## Build Order - -The modules have dependencies on each other and should be built in this order: +### Build Infrastructure -1. `openapi-generator-plugin` (standalone) -2. `api-client-library` (uses the plugin) -3. `examples` (depends on the library) +#### `buildSrc/` +Contains the build logic and custom OpenAPI generator. -## Building the Entire Project +**Purpose**: +- Houses convention plugins (`vertex.java-conventions`, `vertex.openapi-generator`) +- Contains the custom `VertexJavaClientCodegen` generator +- Provides shared build configuration across all modules +- Eliminates circular dependencies and external plugin publishing -To build all modules: -```bash -./gradlew build -``` +**Key Files**: +- `src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java` - Custom OpenAPI generator +- `src/main/groovy/vertex.java-conventions.gradle` - Common Java build settings +- `src/main/groovy/vertex.openapi-generator.gradle` - OpenAPI generation configuration -To clean and rebuild everything: -```bash -./gradlew clean build -``` +## Architecture Benefits -## Publishing +### Convention Plugins +- **Self-contained**: No need to publish plugins separately +- **Consistent**: Shared configuration across all modules +- **Maintainable**: Build logic lives with the code it serves -The library can be published to Maven repositories: -```bash -./gradlew :api-client-library:publishToMavenLocal -./gradlew publish -``` +### Simplified Dependencies +- **No circular dependencies**: buildSrc → modules (not modules → plugins → modules) +- **Faster builds**: No separate plugin build/publish cycle +- **Easier development**: Make changes and build immediately ## Development Workflow -1. Make changes to the OpenAPI generator plugin if needed -2. Build and publish the plugin locally: `./gradlew :openapi-generator-plugin:publishToMavenLocal` -3. Regenerate the API client: `./gradlew :api-client-library:openApiGenerate` -4. Build the library: `./gradlew :api-client-library:build` -5. Test with examples: `./gradlew :examples:run` +The build is completely self-contained: -## Migration Notes +```bash +# Everything needed is built automatically +./gradlew build -This project was converted from a single-module to a multi-module structure: +# Test examples +./gradlew :examples:run -- **Original structure**: All code in `src/main/java/com/vertexvis/` -- **New structure**: - - Code generation logic → `openapi-generator-plugin/` - - Core API client → `api-client-library/` - - Examples → `examples/` +# Publish locally for testing +./gradlew :api-client-library:publishToMavenLocal +``` -The functionality remains the same, but the code is now better organized and the generator can be reused across projects.