From 3dfb8851e238d4fcc950a63bee6d86206a1d4f0e Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Thu, 14 Aug 2025 11:27:40 +0100 Subject: [PATCH 1/2] Added option channelInitializer --- .../settings/YdbConnectionProperties.java | 111 +++++++++++++----- .../jdbc/settings/BadChannelInitilizer.java | 22 ++++ .../settings/CustomChannelInitilizer.java | 16 +++ .../settings/NonSupplierTokenProvider.java | 11 -- .../settings/YdbConnectionPropertiesTest.java | 83 ++++++++++++- 5 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 jdbc/src/test/java/tech/ydb/jdbc/settings/BadChannelInitilizer.java create mode 100644 jdbc/src/test/java/tech/ydb/jdbc/settings/CustomChannelInitilizer.java delete mode 100644 jdbc/src/test/java/tech/ydb/jdbc/settings/NonSupplierTokenProvider.java diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java index 6deab80..0d703bc 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java @@ -3,6 +3,7 @@ import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.Properties; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,6 +55,10 @@ public class YdbConnectionProperties { static final YdbProperty METADATA_URL = YdbProperty.content("metadataURL", "Custom URL for the metadata service authentication"); + static final YdbProperty CHANNEL_INITIALIZER = YdbProperty.object("channelInitializer", + "Custom GRPC channel initilizer, use object instance or class full name impementing" + + " Consumer"); + static final YdbProperty TOKEN_PROVIDER = YdbProperty.object("tokenProvider", "Custom token provider, use object instance or class full name impementing Supplier"); @@ -75,6 +80,7 @@ public class YdbConnectionProperties { private final YdbValue iamEndpoint; private final YdbValue metadataUrl; private final YdbValue tokenProvider; + private final YdbValue channelInitializer; private final YdbValue grpcCompression; public YdbConnectionProperties(String username, String password, Properties props) throws SQLException { @@ -92,6 +98,7 @@ public YdbConnectionProperties(String username, String password, Properties prop this.iamEndpoint = IAM_ENDPOINT.readValue(props); this.metadataUrl = METADATA_URL.readValue(props); this.tokenProvider = TOKEN_PROVIDER.readValue(props); + this.channelInitializer = CHANNEL_INITIALIZER.readValue(props); this.grpcCompression = GRPC_COMPRESSION.readValue(props); } @@ -203,36 +210,12 @@ public GrpcTransportBuilder applyToGrpcTransport(GrpcTransportBuilder builder) t } Object provider = tokenProvider.getValue(); - if (provider instanceof Supplier) { - Supplier prov = (Supplier) provider; - builder = builder.withAuthProvider((rpc) -> () -> prov.get().toString()); - } else if (provider instanceof AuthProvider) { - AuthProvider prov = (AuthProvider) provider; - builder = builder.withAuthProvider(prov); - } else if (provider instanceof String) { - String className = (String) provider; - if (!FQCN.matcher(className).matches()) { - throw new SQLException("tokenProvider must be full class name or instance of Supplier"); - } + builder = applyTokenProvider(builder, provider); + } - try { - Class clazz = Class.forName(className); - if (!Supplier.class.isAssignableFrom(clazz)) { - throw new SQLException("tokenProvider " + className + " is not implement Supplier"); - } - Supplier prov = clazz.asSubclass(Supplier.class) - .getConstructor(new Class[0]) - .newInstance(new Object[0]); - builder = builder.withAuthProvider((rpc) -> () -> prov.get().toString()); - } catch (ClassNotFoundException ex) { - throw new SQLException("tokenProvider " + className + " not found", ex); - } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException ex) { - throw new SQLException("Cannot construct tokenProvider " + className, ex); - } - } else if (provider != null) { - throw new SQLException("Cannot parse tokenProvider " + provider.getClass().getName()); - } + if (channelInitializer.hasValue()) { + Object initializer = channelInitializer.getValue(); + builder = applyChannelInitializer(builder, initializer); } if (grpcCompression.hasValue()) { @@ -248,4 +231,74 @@ public GrpcTransportBuilder applyToGrpcTransport(GrpcTransportBuilder builder) t return builder; } + + private GrpcTransportBuilder applyTokenProvider(GrpcTransportBuilder builder, Object provider) throws SQLException { + if (provider instanceof Supplier) { + Supplier prov = (Supplier) provider; + builder = builder.withAuthProvider((rpc) -> () -> prov.get().toString()); + } else if (provider instanceof AuthProvider) { + AuthProvider prov = (AuthProvider) provider; + builder = builder.withAuthProvider(prov); + } else if (provider instanceof String) { + String className = (String) provider; + if (!FQCN.matcher(className).matches()) { + throw new SQLException("tokenProvider must be full class name or instance of Supplier"); + } + + try { + Class clazz = Class.forName(className); + if (!Supplier.class.isAssignableFrom(clazz)) { + throw new SQLException("tokenProvider " + className + " is not implement Supplier"); + } + Supplier prov = clazz.asSubclass(Supplier.class) + .getConstructor(new Class[0]) + .newInstance(new Object[0]); + builder = builder.withAuthProvider((rpc) -> () -> prov.get().toString()); + } catch (ClassNotFoundException ex) { + throw new SQLException("tokenProvider " + className + " not found", ex); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new SQLException("Cannot construct tokenProvider " + className, ex); + } + } else if (provider != null) { + throw new SQLException("Cannot parse tokenProvider " + provider.getClass().getName()); + } + return builder; + } + + private GrpcTransportBuilder applyChannelInitializer(GrpcTransportBuilder builder, Object initializer) + throws SQLException { + if (initializer instanceof Consumer) { + @SuppressWarnings("unchecked") + Consumer prov = (Consumer) initializer; + builder = builder.addChannelInitializer(prov); + } else if (initializer instanceof String) { + String className = (String) initializer; + if (!FQCN.matcher(className).matches()) { + throw new SQLException("channelInitializer must be full class name or instance of " + + "Consumer"); + } + + try { + Class clazz = Class.forName(className); + if (!Consumer.class.isAssignableFrom(clazz)) { + throw new SQLException("channelInitializer " + className + " is not implement " + + "Consumer"); + } + @SuppressWarnings("unchecked") + Consumer prov = clazz.asSubclass(Consumer.class) + .getConstructor(new Class[0]) + .newInstance(new Object[0]); + builder = builder.addChannelInitializer(prov); + } catch (ClassNotFoundException ex) { + throw new SQLException("channelInitializer " + className + " not found", ex); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new SQLException("Cannot construct channelInitializer " + className, ex); + } + } else if (initializer != null) { + throw new SQLException("Cannot parse channelInitializer " + initializer.getClass().getName()); + } + return builder; + } } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/BadChannelInitilizer.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/BadChannelInitilizer.java new file mode 100644 index 0000000..d8d202d --- /dev/null +++ b/jdbc/src/test/java/tech/ydb/jdbc/settings/BadChannelInitilizer.java @@ -0,0 +1,22 @@ +package tech.ydb.jdbc.settings; + +import java.util.function.Consumer; + +import io.grpc.ManagedChannelBuilder; + +/** + * + * @author Aleksandr Gorshenin + */ +public class BadChannelInitilizer implements Consumer> { + private final String authority; + + public BadChannelInitilizer(String authority) { + this.authority = authority; + } + + @Override + public void accept(ManagedChannelBuilder t) { + t.overrideAuthority(authority); + } +} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/CustomChannelInitilizer.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/CustomChannelInitilizer.java new file mode 100644 index 0000000..9619b3a --- /dev/null +++ b/jdbc/src/test/java/tech/ydb/jdbc/settings/CustomChannelInitilizer.java @@ -0,0 +1,16 @@ +package tech.ydb.jdbc.settings; + +import java.util.function.Consumer; + +import io.grpc.ManagedChannelBuilder; + +/** + * + * @author Aleksandr Gorshenin + */ +public class CustomChannelInitilizer implements Consumer> { + @Override + public void accept(ManagedChannelBuilder t) { + t.usePlaintext(); + } +} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/NonSupplierTokenProvider.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/NonSupplierTokenProvider.java deleted file mode 100644 index 16b7f39..0000000 --- a/jdbc/src/test/java/tech/ydb/jdbc/settings/NonSupplierTokenProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package tech.ydb.jdbc.settings; - -/** - * - * @author Aleksandr Gorshenin - */ -public class NonSupplierTokenProvider { - public String getToken() { - return "STATIC"; - } -} diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java index c436894..529d85c 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java @@ -2,8 +2,10 @@ import java.sql.SQLException; import java.util.Properties; +import java.util.function.Consumer; import java.util.function.Supplier; +import io.grpc.ManagedChannelBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -100,10 +102,10 @@ public void tokenProviderInvalidClassTest() throws SQLException { @Test public void tokenProviderNonSupplierClassTest() throws SQLException { Properties props = new Properties(); - props.put("tokenProvider", "tech.ydb.jdbc.settings.NonSupplierTokenProvider"); + props.put("tokenProvider", "tech.ydb.jdbc.settings.CustomChannelInitilizer"); YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); ExceptionAssert.sqlException( - "tokenProvider tech.ydb.jdbc.settings.NonSupplierTokenProvider is not implement Supplier", + "tokenProvider tech.ydb.jdbc.settings.CustomChannelInitilizer is not implement Supplier", () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) ); } @@ -119,4 +121,81 @@ public void tokenProviderWrongObjectTest() throws SQLException { () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) ); } + + @Test + public void channelInitializerConsumerTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", (Consumer>) (ManagedChannelBuilder t) -> { + t.maxTraceEvents(0); + }); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + GrpcTransportBuilder builder = cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)); + Assertions.assertFalse(builder.getChannelInitializers().isEmpty()); + } + + @Test + public void channelInitializerClassTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.CustomChannelInitilizer"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + GrpcTransportBuilder builder = cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)); + Assertions.assertFalse(builder.getChannelInitializers().isEmpty()); + } + + @Test + public void channelInitializerWrongClassNameTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "1tech.ydb.jdbc.settings.StaticTokenProvider"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + ExceptionAssert.sqlException( + "channelInitializer must be full class name or instance of Consumer", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } + + @Test + public void channelInitializerUnknownClassTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.TestTokenProvider"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + ExceptionAssert.sqlException( + "channelInitializer tech.ydb.jdbc.settings.TestTokenProvider not found", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } + + @Test + public void channelInitializerInvalidClassTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.BadChannelInitilizer"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + ExceptionAssert.sqlException( + "Cannot construct channelInitializer tech.ydb.jdbc.settings.BadChannelInitilizer", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } + + @Test + public void channelInitializerNonConsumerClassTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.StaticTokenProvider"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + ExceptionAssert.sqlException( + "channelInitializer tech.ydb.jdbc.settings.StaticTokenProvider is not implement " + + "Consumer", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } + + @Test + public void channelInitializerWrongObjectTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", new Object()); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + + ExceptionAssert.sqlException( + "Cannot parse channelInitializer java.lang.Object", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } } From 8cc58f42bfd28f3d137cbb2ac308649c6914c3cf Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Thu, 14 Aug 2025 11:43:11 +0100 Subject: [PATCH 2/2] Added support of class names list --- .../settings/YdbConnectionProperties.java | 51 ++++++++++++------- .../settings/YdbConnectionPropertiesTest.java | 24 ++++++++- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java index 0d703bc..32ab9e9 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java @@ -274,31 +274,46 @@ private GrpcTransportBuilder applyChannelInitializer(GrpcTransportBuilder builde builder = builder.addChannelInitializer(prov); } else if (initializer instanceof String) { String className = (String) initializer; - if (!FQCN.matcher(className).matches()) { - throw new SQLException("channelInitializer must be full class name or instance of " - + "Consumer"); - } - try { - Class clazz = Class.forName(className); - if (!Consumer.class.isAssignableFrom(clazz)) { - throw new SQLException("channelInitializer " + className + " is not implement " + if (FQCN.matcher(className.trim()).matches()) { + builder.addChannelInitializer(newInitializerInstance(className.trim())); + } else { + String[] classNames = className.split(","); + if (classNames.length < 2) { + throw new SQLException("channelInitializer must be full class name or instance of " + "Consumer"); } - @SuppressWarnings("unchecked") - Consumer prov = clazz.asSubclass(Consumer.class) - .getConstructor(new Class[0]) - .newInstance(new Object[0]); - builder = builder.addChannelInitializer(prov); - } catch (ClassNotFoundException ex) { - throw new SQLException("channelInitializer " + className + " not found", ex); - } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException ex) { - throw new SQLException("Cannot construct channelInitializer " + className, ex); + + for (String name: classNames) { + if (!FQCN.matcher(name.trim()).matches()) { + throw new SQLException("channelInitializer must be full class name or instance of " + + "Consumer"); + } + builder.addChannelInitializer(newInitializerInstance(name.trim())); + } } } else if (initializer != null) { throw new SQLException("Cannot parse channelInitializer " + initializer.getClass().getName()); } return builder; } + + @SuppressWarnings("unchecked") + private Consumer newInitializerInstance(String className) throws SQLException { + try { + Class clazz = Class.forName(className); + if (!Consumer.class.isAssignableFrom(clazz)) { + throw new SQLException("channelInitializer " + className + " is not implement " + + "Consumer"); + } + return clazz.asSubclass(Consumer.class) + .getConstructor(new Class[0]) + .newInstance(new Object[0]); + } catch (ClassNotFoundException ex) { + throw new SQLException("channelInitializer " + className + " not found", ex); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new SQLException("Cannot construct channelInitializer " + className, ex); + } + } } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java index 529d85c..54c51bf 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/settings/YdbConnectionPropertiesTest.java @@ -139,7 +139,17 @@ public void channelInitializerClassTest() throws SQLException { props.put("channelInitializer", "tech.ydb.jdbc.settings.CustomChannelInitilizer"); YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); GrpcTransportBuilder builder = cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)); - Assertions.assertFalse(builder.getChannelInitializers().isEmpty()); + Assertions.assertEquals(1, builder.getChannelInitializers().size()); + } + + @Test + public void channelInitializerClassListTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.CustomChannelInitilizer, " + + "tech.ydb.jdbc.settings.CustomChannelInitilizer"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + GrpcTransportBuilder builder = cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)); + Assertions.assertEquals(2, builder.getChannelInitializers().size()); } @Test @@ -153,6 +163,18 @@ public void channelInitializerWrongClassNameTest() throws SQLException { ); } + @Test + public void channelInitializerWrongClassNameListTest() throws SQLException { + Properties props = new Properties(); + props.put("channelInitializer", "tech.ydb.jdbc.settings.CustomChannelInitilizer, " + + "1tech.ydb.jdbc.settings.StaticTokenProvider"); + YdbConnectionProperties cp = new YdbConnectionProperties(null, null, props); + ExceptionAssert.sqlException( + "channelInitializer must be full class name or instance of Consumer", + () -> cp.applyToGrpcTransport(GrpcTransport.forConnectionString(YDB_URL)) + ); + } + @Test public void channelInitializerUnknownClassTest() throws SQLException { Properties props = new Properties();