diff --git a/generators/java-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts b/generators/java-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts index f526815ede65..503063af09c8 100644 --- a/generators/java-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts +++ b/generators/java-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts @@ -25,10 +25,12 @@ const RESERVED_NAMES = new Set([ "import", "for", "assert", - "switch", - "getClass" + "switch" ]); +// Method names that conflict with final methods in Java's Object class +const RESERVED_METHOD_NAMES = new Set(["getClass", "notify", "notifyAll", "wait"]); + export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGeneratorContext { public ir: FernIr.dynamic.DynamicIntermediateRepresentation; public customConfig: BaseJavaCustomConfigSchema; @@ -74,7 +76,12 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene } public getMethodName(name: FernIr.Name): string { - return this.getName(name.camelCase.safeName); + const methodName = name.camelCase.safeName; + // Use suffix for reserved method names to match Java v1 generator behavior + if (this.isReservedMethodName(methodName)) { + return methodName + "_"; + } + return this.getName(methodName); } public getRootClientClassReference(): java.ClassReference { @@ -446,4 +453,8 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene private isReservedName(name: string): boolean { return RESERVED_NAMES.has(name); } + + private isReservedMethodName(name: string): boolean { + return RESERVED_METHOD_NAMES.has(name); + } } diff --git a/generators/java-v2/sdk/src/readme/ReadmeSnippetBuilder.ts b/generators/java-v2/sdk/src/readme/ReadmeSnippetBuilder.ts index b746bd884790..31e5e70e578c 100644 --- a/generators/java-v2/sdk/src/readme/ReadmeSnippetBuilder.ts +++ b/generators/java-v2/sdk/src/readme/ReadmeSnippetBuilder.ts @@ -195,7 +195,8 @@ This SDK supports two authentication methods: If you already have a valid access token, you can use it directly: \`\`\`java -${clientClassName} client = ${clientClassName}.withToken("your-access-token") +${clientClassName} client = ${clientClassName}.builder() + .token("your-access-token") .url("https://api.example.com") .build(); \`\`\` @@ -205,7 +206,8 @@ ${clientClassName} client = ${clientClassName}.withToken("your-access-token") The SDK can automatically handle token acquisition and refresh: \`\`\`java -${clientClassName} client = ${clientClassName}.withCredentials("client-id", "client-secret") +${clientClassName} client = ${clientClassName}.builder() + .credentials("client-id", "client-secret") .url("https://api.example.com") .build(); \`\`\``; @@ -808,7 +810,9 @@ ${clientClassName} client = ${clientClassName}.withCredentials("client-id", "cli } private getAccessFromRootClient(fernFilepath: FernFilepath): java.AstNode { - const clientAccessParts = fernFilepath.allParts.map((part) => part.camelCase.safeName + "()"); + const clientAccessParts = fernFilepath.allParts.map( + (part) => this.getKeyWordCompatibleMethodName(part.camelCase.safeName) + "()" + ); return clientAccessParts.length > 0 ? java.codeblock(`${ReadmeSnippetBuilder.CLIENT_VARIABLE_NAME}.${clientAccessParts.join(".")}`) : java.codeblock(ReadmeSnippetBuilder.CLIENT_VARIABLE_NAME); @@ -933,7 +937,8 @@ ${clientClassName} client = ${clientClassName}.withCredentials("client-id", "cli // Get access path to WebSocket client from root client const clientAccessParts = fernFilepath.allParts.map( - (part: { camelCase: { safeName: string } }) => part.camelCase.safeName + "()" + (part: { camelCase: { safeName: string } }) => + this.getKeyWordCompatibleMethodName(part.camelCase.safeName) + "()" ); const wsClientAccess = clientAccessParts.length > 0 @@ -1077,4 +1082,13 @@ ${clientClassName} client = ${clientClassName}.withCredentials("client-id", "cli return this.renderSnippet(snippet); } + + private static RESERVED_METHOD_NAMES = new Set(["getClass", "notify", "notifyAll", "wait"]); + + private getKeyWordCompatibleMethodName(methodName: string): string { + if (ReadmeSnippetBuilder.RESERVED_METHOD_NAMES.has(methodName)) { + return methodName + "_"; + } + return methodName; + } } diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/utils/KeyWordUtils.java b/generators/java/generator-utils/src/main/java/com/fern/java/utils/KeyWordUtils.java index c24b7a70f811..fe12d715234d 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/utils/KeyWordUtils.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/utils/KeyWordUtils.java @@ -33,7 +33,7 @@ public final class KeyWordUtils { "assert", "switch"); - private static final Set RESERVED_METHOD_NAMES = Set.of("getClass"); + private static final Set RESERVED_METHOD_NAMES = Set.of("getClass", "notify", "notifyAll", "wait"); private KeyWordUtils() {} diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractClientGeneratorUtils.java b/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractClientGeneratorUtils.java index 4c26eb2b48e2..3cfed9332d36 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractClientGeneratorUtils.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractClientGeneratorUtils.java @@ -35,6 +35,7 @@ import com.fern.java.output.GeneratedJavaFile; import com.fern.java.output.GeneratedJavaInterface; import com.fern.java.output.GeneratedObjectMapper; +import com.fern.java.utils.KeyWordUtils; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; @@ -373,7 +374,8 @@ public Result buildClients() { } private MethodSpec.Builder getBaseSubpackageMethod(Subpackage subpackage, ClassName subpackageClientInterface) { - return MethodSpec.methodBuilder(subpackage.getName().getCamelCase().getSafeName()) + return MethodSpec.methodBuilder(KeyWordUtils.getKeyWordCompatibleMethodName( + subpackage.getName().getCamelCase().getSafeName())) .addModifiers(Modifier.PUBLIC) .returns(subpackageClientInterface); } diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractRootClientGenerator.java b/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractRootClientGenerator.java index 8a8cab91a9cf..b44f5195cdac 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractRootClientGenerator.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/generators/AbstractRootClientGenerator.java @@ -283,6 +283,7 @@ public Boolean _visitUnknown(Object unknownType) { // For staged builder, add static factory methods that delegate to builder class ClassName tokenAuthClassName = builderName.nestedClass("_TokenAuth"); ClassName credentialsAuthClassName = builderName.nestedClass("_CredentialsAuth"); + ClassName builderStageClassName = builderName.nestedClass("_Builder"); result.getClientImpl() .addMethod(MethodSpec.methodBuilder("withToken") @@ -307,6 +308,15 @@ public Boolean _visitUnknown(Object unknownType) { .returns(credentialsAuthClassName) .addStatement("return $T.withCredentials(clientId, clientSecret)", builderName) .build()); + + result.getClientImpl() + .addMethod(MethodSpec.methodBuilder("builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addJavadoc("Creates a new client builder.\n") + .addJavadoc("@return A builder for configuring and creating the client") + .returns(builderStageClassName) + .addStatement("return $T.builder()", builderName) + .build()); } else { result.getClientImpl() .addMethod(MethodSpec.methodBuilder("builder") @@ -1464,6 +1474,191 @@ private void generateStagedBuilderForOAuth( clientBuilder.addMethod(withCredentialsMethod.build()); + // Add builder() factory method to base builder + ClassName builderStageClassName = builderName.nestedClass("_Builder"); + clientBuilder.addMethod(MethodSpec.methodBuilder("builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addJavadoc("Creates a new client builder.\n") + .addJavadoc("Use this method to start building a client with the classic builder pattern.\n") + .addJavadoc("\n") + .addJavadoc("@return A builder for configuring authentication and creating the client") + .returns(builderStageClassName) + .addStatement("return new _Builder()") + .build()); + + // Create _Builder nested class with all builder methods plus token() and credentials() + TypeSpec.Builder builderStageBuilder = TypeSpec.classBuilder("_Builder") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + + // Add fields to store configuration values + builderStageBuilder.addField(FieldSpec.builder(generatedEnvironmentsClass.getClassName(), "environment") + .addModifiers(Modifier.PRIVATE) + .build()); + + builderStageBuilder.addField(FieldSpec.builder( + ParameterizedTypeName.get(ClassName.get(Optional.class), ClassName.get(Integer.class)), + "timeout") + .addModifiers(Modifier.PRIVATE) + .initializer("$T.empty()", Optional.class) + .build()); + + builderStageBuilder.addField(FieldSpec.builder( + ParameterizedTypeName.get(ClassName.get(Optional.class), ClassName.get(Integer.class)), + "maxRetries") + .addModifiers(Modifier.PRIVATE) + .initializer("$T.empty()", Optional.class) + .build()); + + builderStageBuilder.addField(FieldSpec.builder(OkHttpClient.class, "httpClient") + .addModifiers(Modifier.PRIVATE) + .build()); + + builderStageBuilder.addField(FieldSpec.builder( + ParameterizedTypeName.get( + ClassName.get(Map.class), + ClassName.get(String.class), + ClassName.get(String.class)), + "headers") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .initializer("new $T<>()", HashMap.class) + .build()); + + // Add environment() method if environments are present + if (generatedEnvironmentsClass.optionsPresent()) { + builderStageBuilder.addMethod(MethodSpec.methodBuilder("environment") + .addModifiers(Modifier.PUBLIC) + .addParameter(generatedEnvironmentsClass.getClassName(), "environment") + .returns(builderStageClassName) + .addStatement("this.environment = environment") + .addStatement("return this") + .build()); + } + + // Add url() method if single URL environment + if (generatedEnvironmentsClass.info() instanceof SingleUrlEnvironmentClass) { + SingleUrlEnvironmentClass singleUrlEnvClass = + ((SingleUrlEnvironmentClass) generatedEnvironmentsClass.info()); + builderStageBuilder.addMethod(MethodSpec.methodBuilder("url") + .addModifiers(Modifier.PUBLIC) + .addParameter(String.class, "url") + .returns(builderStageClassName) + .addStatement( + "this.environment = $T.$N(url)", + generatedEnvironmentsClass.getClassName(), + singleUrlEnvClass.getCustomMethod()) + .addStatement("return this") + .build()); + } + + // Add timeout() method + builderStageBuilder.addMethod(MethodSpec.methodBuilder("timeout") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Sets the timeout (in seconds) for the client. Defaults to 60 seconds.") + .addParameter(int.class, "timeout") + .returns(builderStageClassName) + .addStatement("this.timeout = $T.of(timeout)", Optional.class) + .addStatement("return this") + .build()); + + // Add maxRetries() method + builderStageBuilder.addMethod(MethodSpec.methodBuilder("maxRetries") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Sets the maximum number of retries for the client. Defaults to 2 retries.") + .addParameter(int.class, "maxRetries") + .returns(builderStageClassName) + .addStatement("this.maxRetries = $T.of(maxRetries)", Optional.class) + .addStatement("return this") + .build()); + + // Add httpClient() method + builderStageBuilder.addMethod(MethodSpec.methodBuilder("httpClient") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Sets the underlying OkHttp client") + .addParameter(OkHttpClient.class, "httpClient") + .returns(builderStageClassName) + .addStatement("this.httpClient = httpClient") + .addStatement("return this") + .build()); + + // Add addHeader() method + builderStageBuilder.addMethod(MethodSpec.methodBuilder("addHeader") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Add a custom header to be sent with all requests.\n") + .addJavadoc("@param name The header name\n") + .addJavadoc("@param value The header value\n") + .addJavadoc("@return This builder for method chaining") + .addParameter(String.class, "name") + .addParameter(String.class, "value") + .returns(builderStageClassName) + .addStatement("this.headers.put(name, value)") + .addStatement("return this") + .build()); + + // Add token() method that returns _TokenAuth with configuration copied using setter methods + MethodSpec.Builder tokenMethodBuilder = MethodSpec.methodBuilder("token") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Configure the client to use a pre-generated access token for authentication.\n") + .addJavadoc("Use this when you already have a valid access token and want to bypass\n") + .addJavadoc("the OAuth client credentials flow.\n") + .addJavadoc("\n") + .addJavadoc( + "@param $L The access token to use for Authorization header\n", + tokenOverridePropertyName) + .addJavadoc("@return A builder configured for token authentication") + .addParameter(String.class, tokenOverridePropertyName) + .returns(tokenAuthClassName) + .addStatement("_TokenAuth auth = new _TokenAuth($L)", tokenOverridePropertyName) + .beginControlFlow("if (this.environment != null)") + .addStatement("auth.environment = this.environment") + .endControlFlow() + .beginControlFlow("if (this.timeout.isPresent())") + .addStatement("auth.timeout(this.timeout.get())") + .endControlFlow() + .beginControlFlow("if (this.maxRetries.isPresent())") + .addStatement("auth.maxRetries(this.maxRetries.get())") + .endControlFlow() + .beginControlFlow("if (this.httpClient != null)") + .addStatement("auth.httpClient(this.httpClient)") + .endControlFlow() + .beginControlFlow("for ($T.Entry header : this.headers.entrySet())", Map.class) + .addStatement("auth.addHeader(header.getKey(), header.getValue())") + .endControlFlow() + .addStatement("return auth"); + builderStageBuilder.addMethod(tokenMethodBuilder.build()); + + // Add credentials() method that returns _CredentialsAuth with configuration copied using setter methods + MethodSpec.Builder credentialsMethodBuilder = MethodSpec.methodBuilder("credentials") + .addModifiers(Modifier.PUBLIC) + .addJavadoc("Configure the client to use OAuth client credentials for authentication.\n") + .addJavadoc("The builder will automatically handle token acquisition and refresh.\n") + .addJavadoc("\n") + .addJavadoc("@param clientId The OAuth client ID\n") + .addJavadoc("@param clientSecret The OAuth client secret\n") + .addJavadoc("@return A builder configured for OAuth client credentials authentication") + .addParameter(String.class, "clientId") + .addParameter(String.class, "clientSecret") + .returns(credentialsAuthClassName) + .addStatement("_CredentialsAuth auth = new _CredentialsAuth(clientId, clientSecret)") + .beginControlFlow("if (this.environment != null)") + .addStatement("auth.environment = this.environment") + .endControlFlow() + .beginControlFlow("if (this.timeout.isPresent())") + .addStatement("auth.timeout(this.timeout.get())") + .endControlFlow() + .beginControlFlow("if (this.maxRetries.isPresent())") + .addStatement("auth.maxRetries(this.maxRetries.get())") + .endControlFlow() + .beginControlFlow("if (this.httpClient != null)") + .addStatement("auth.httpClient(this.httpClient)") + .endControlFlow() + .beginControlFlow("for ($T.Entry header : this.headers.entrySet())", Map.class) + .addStatement("auth.addHeader(header.getKey(), header.getValue())") + .endControlFlow() + .addStatement("return auth"); + builderStageBuilder.addMethod(credentialsMethodBuilder.build()); + + clientBuilder.addType(builderStageBuilder.build()); + // Make configureAuthMethod empty for base class (subclasses override) if (configureAuthMethod != null) { // Base class setAuthentication is empty - subclasses provide the implementation diff --git a/generators/java/sdk/versions.yml b/generators/java/sdk/versions.yml index c5ee9d197e2b..a5f337718b5a 100644 --- a/generators/java/sdk/versions.yml +++ b/generators/java/sdk/versions.yml @@ -1,4 +1,37 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.29.2 + changelogEntry: + - summary: | + Add `notify`, `notifyAll`, and `wait` to reserved method names to prevent generated SDK + methods from conflicting with final methods in Java's Object class. This fixes compilation + errors when an API has a subpackage named "notify" (e.g., Twilio's Notify API). + type: fix + createdAt: "2026-01-15" + irVersion: 63 + +- version: 3.29.1 + changelogEntry: + - summary: | + Fix `_Builder` class to support all builder methods (url, timeout, environment, maxRetries, + httpClient, addHeader) so method chaining works in any order. This allows customers upgrading + from 3.18.x who wrote `builder().url().token()` to continue working without compile errors. + Configuration values set on `_Builder` are now properly passed through to `_TokenAuth` and + `_CredentialsAuth` when `token()` or `credentials()` is called. + type: fix + createdAt: "2026-01-15" + irVersion: 63 + +- version: 3.29.0 + changelogEntry: + - summary: | + Add backward-compatible `builder()` method for OAuth client credentials authentication. + This restores support for the classic builder pattern `Client.builder().token("...")` and + `Client.builder().credentials("...", "...")` alongside the existing `withToken()` and + `withCredentials()` shortcuts. This prevents breaking changes for customers who upgraded. + type: feat + createdAt: "2026-01-14" + irVersion: 63 + - version: 3.28.2 changelogEntry: - summary: Update Dockerfile to use the latest generator-cli with improve reference.md generation. diff --git a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/README.md b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/README.md index 0b190e262137..e9fd4a55173f 100644 --- a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/README.md +++ b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/README.md @@ -84,7 +84,8 @@ This SDK supports two authentication methods: If you already have a valid access token, you can use it directly: ```java -SeedOauthClientCredentialsClient client = SeedOauthClientCredentialsClient.withToken("your-access-token") +SeedOauthClientCredentialsClient client = SeedOauthClientCredentialsClient.builder() + .token("your-access-token") .url("https://api.example.com") .build(); ``` @@ -94,7 +95,8 @@ SeedOauthClientCredentialsClient client = SeedOauthClientCredentialsClient.withT The SDK can automatically handle token acquisition and refresh: ```java -SeedOauthClientCredentialsClient client = SeedOauthClientCredentialsClient.withCredentials("client-id", "client-secret") +SeedOauthClientCredentialsClient client = SeedOauthClientCredentialsClient.builder() + .credentials("client-id", "client-secret") .url("https://api.example.com") .build(); ``` diff --git a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClient.java b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClient.java index f9f377eac451..001f4cee96c4 100644 --- a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClient.java +++ b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClient.java @@ -65,4 +65,12 @@ public static AsyncSeedOauthClientCredentialsClientBuilder._CredentialsAuth with String clientId, String clientSecret) { return AsyncSeedOauthClientCredentialsClientBuilder.withCredentials(clientId, clientSecret); } + + /** + * Creates a new client builder. + * @return A builder for configuring and creating the client + */ + public static AsyncSeedOauthClientCredentialsClientBuilder._Builder builder() { + return AsyncSeedOauthClientCredentialsClientBuilder.builder(); + } } diff --git a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClientBuilder.java b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClientBuilder.java index c4d94572c9c6..959807f39d87 100644 --- a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClientBuilder.java +++ b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/AsyncSeedOauthClientCredentialsClientBuilder.java @@ -47,6 +47,16 @@ public static _CredentialsAuth withCredentials(String clientId, String clientSec return new _CredentialsAuth(clientId, clientSecret); } + /** + * Creates a new client builder. + * Use this method to start building a client with the classic builder pattern. + * + * @return A builder for configuring authentication and creating the client + */ + public static _Builder builder() { + return new _Builder(); + } + public AsyncSeedOauthClientCredentialsClientBuilder url(String url) { this.environment = Environment.custom(url); return this; @@ -250,4 +260,112 @@ public AsyncSeedOauthClientCredentialsClient build() { return new AsyncSeedOauthClientCredentialsClient(finalOptions); } } + + public static final class _Builder { + private Environment environment; + + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private OkHttpClient httpClient; + + private final Map headers = new HashMap<>(); + + public _Builder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public _Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public _Builder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public _Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public _Builder addHeader(String name, String value) { + this.headers.put(name, value); + return this; + } + + /** + * Configure the client to use a pre-generated access token for authentication. + * Use this when you already have a valid access token and want to bypass + * the OAuth client credentials flow. + * + * @param token The access token to use for Authorization header + * @return A builder configured for token authentication + */ + public _TokenAuth token(String token) { + _TokenAuth auth = new _TokenAuth(token); + if (this.environment != null) { + auth.environment = this.environment; + } + if (this.timeout.isPresent()) { + auth.timeout(this.timeout.get()); + } + if (this.maxRetries.isPresent()) { + auth.maxRetries(this.maxRetries.get()); + } + if (this.httpClient != null) { + auth.httpClient(this.httpClient); + } + for (Map.Entry header : this.headers.entrySet()) { + auth.addHeader(header.getKey(), header.getValue()); + } + return auth; + } + + /** + * Configure the client to use OAuth client credentials for authentication. + * The builder will automatically handle token acquisition and refresh. + * + * @param clientId The OAuth client ID + * @param clientSecret The OAuth client secret + * @return A builder configured for OAuth client credentials authentication + */ + public _CredentialsAuth credentials(String clientId, String clientSecret) { + _CredentialsAuth auth = new _CredentialsAuth(clientId, clientSecret); + if (this.environment != null) { + auth.environment = this.environment; + } + if (this.timeout.isPresent()) { + auth.timeout(this.timeout.get()); + } + if (this.maxRetries.isPresent()) { + auth.maxRetries(this.maxRetries.get()); + } + if (this.httpClient != null) { + auth.httpClient(this.httpClient); + } + for (Map.Entry header : this.headers.entrySet()) { + auth.addHeader(header.getKey(), header.getValue()); + } + return auth; + } + } } diff --git a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClient.java b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClient.java index 67b12bf426bd..26ad2220ab96 100644 --- a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClient.java +++ b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClient.java @@ -65,4 +65,12 @@ public static SeedOauthClientCredentialsClientBuilder._CredentialsAuth withCrede String clientId, String clientSecret) { return SeedOauthClientCredentialsClientBuilder.withCredentials(clientId, clientSecret); } + + /** + * Creates a new client builder. + * @return A builder for configuring and creating the client + */ + public static SeedOauthClientCredentialsClientBuilder._Builder builder() { + return SeedOauthClientCredentialsClientBuilder.builder(); + } } diff --git a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClientBuilder.java b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClientBuilder.java index 45f15e265a19..d1b57637ef2d 100644 --- a/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClientBuilder.java +++ b/seed/java-sdk/oauth-client-credentials/oauth-client-credentials/src/main/java/com/seed/oauthClientCredentials/SeedOauthClientCredentialsClientBuilder.java @@ -47,6 +47,16 @@ public static _CredentialsAuth withCredentials(String clientId, String clientSec return new _CredentialsAuth(clientId, clientSecret); } + /** + * Creates a new client builder. + * Use this method to start building a client with the classic builder pattern. + * + * @return A builder for configuring authentication and creating the client + */ + public static _Builder builder() { + return new _Builder(); + } + public SeedOauthClientCredentialsClientBuilder url(String url) { this.environment = Environment.custom(url); return this; @@ -250,4 +260,112 @@ public SeedOauthClientCredentialsClient build() { return new SeedOauthClientCredentialsClient(finalOptions); } } + + public static final class _Builder { + private Environment environment; + + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private OkHttpClient httpClient; + + private final Map headers = new HashMap<>(); + + public _Builder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public _Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public _Builder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public _Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public _Builder addHeader(String name, String value) { + this.headers.put(name, value); + return this; + } + + /** + * Configure the client to use a pre-generated access token for authentication. + * Use this when you already have a valid access token and want to bypass + * the OAuth client credentials flow. + * + * @param token The access token to use for Authorization header + * @return A builder configured for token authentication + */ + public _TokenAuth token(String token) { + _TokenAuth auth = new _TokenAuth(token); + if (this.environment != null) { + auth.environment = this.environment; + } + if (this.timeout.isPresent()) { + auth.timeout(this.timeout.get()); + } + if (this.maxRetries.isPresent()) { + auth.maxRetries(this.maxRetries.get()); + } + if (this.httpClient != null) { + auth.httpClient(this.httpClient); + } + for (Map.Entry header : this.headers.entrySet()) { + auth.addHeader(header.getKey(), header.getValue()); + } + return auth; + } + + /** + * Configure the client to use OAuth client credentials for authentication. + * The builder will automatically handle token acquisition and refresh. + * + * @param clientId The OAuth client ID + * @param clientSecret The OAuth client secret + * @return A builder configured for OAuth client credentials authentication + */ + public _CredentialsAuth credentials(String clientId, String clientSecret) { + _CredentialsAuth auth = new _CredentialsAuth(clientId, clientSecret); + if (this.environment != null) { + auth.environment = this.environment; + } + if (this.timeout.isPresent()) { + auth.timeout(this.timeout.get()); + } + if (this.maxRetries.isPresent()) { + auth.maxRetries(this.maxRetries.get()); + } + if (this.httpClient != null) { + auth.httpClient(this.httpClient); + } + for (Map.Entry header : this.headers.entrySet()) { + auth.addHeader(header.getKey(), header.getValue()); + } + return auth; + } + } }