2929import com .clickhouse .client .api .internal .SettingsConverter ;
3030import com .clickhouse .client .api .internal .TableSchemaParser ;
3131import com .clickhouse .client .api .internal .ValidationUtils ;
32+ import com .clickhouse .client .api .metadata .ColumnToMethodMatchingStrategy ;
33+ import com .clickhouse .client .api .metadata .DefaultColumnToMethodMatchingStrategy ;
3234import com .clickhouse .client .api .metadata .TableSchema ;
3335import com .clickhouse .client .api .metrics .ClientMetrics ;
3436import com .clickhouse .client .api .metrics .OperationMetrics ;
@@ -146,8 +148,10 @@ public class Client implements AutoCloseable {
146148 private Map <String , TableSchema > tableSchemaCache = new ConcurrentHashMap <>();
147149 private Map <String , Boolean > tableSchemaHasDefaults = new ConcurrentHashMap <>();
148150
151+ private final ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy ;
152+
149153 private Client (Set <String > endpoints , Map <String ,String > configuration , boolean useNewImplementation ,
150- ExecutorService sharedOperationExecutor ) {
154+ ExecutorService sharedOperationExecutor , ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy ) {
151155 this .endpoints = endpoints ;
152156 this .configuration = configuration ;
153157 this .endpoints .forEach (endpoint -> {
@@ -170,6 +174,7 @@ private Client(Set<String> endpoints, Map<String,String> configuration, boolean
170174 this .oldClient = ClientV1AdaptorHelper .createClient (configuration );
171175 LOG .info ("Using old http client implementation" );
172176 }
177+ this .columnToMethodMatchingStrategy = columnToMethodMatchingStrategy ;
173178 }
174179
175180 /**
@@ -211,6 +216,7 @@ public static class Builder {
211216 private boolean useNewImplementation = true ;
212217
213218 private ExecutorService sharedOperationExecutor = null ;
219+ private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy ;
214220
215221 public Builder () {
216222 this .endpoints = new HashSet <>();
@@ -320,6 +326,17 @@ public Builder setAccessToken(String accessToken) {
320326 return this ;
321327 }
322328
329+ /**
330+ * Makes client to use SSL Client Certificate to authenticate with server.
331+ * Client certificate should be set as well. {@link Client.Builder#setClientCertificate(String)}
332+ * @param useSSLAuthentication
333+ * @return
334+ */
335+ public Builder useSSLAuthentication (boolean useSSLAuthentication ) {
336+ this .configuration .put ("ssl_authentication" , String .valueOf (useSSLAuthentication ));
337+ return this ;
338+ }
339+
323340 /**
324341 * Configures client to use build-in connection pool
325342 * @param enable - if connection pool should be enabled
@@ -846,6 +863,18 @@ public Builder serverSetting(String name, Collection<String> values) {
846863 return this ;
847864 }
848865
866+ /**
867+ * Sets column to method matching strategy. It is used while registering POJO serializers and deserializers.
868+ * Default is {@link DefaultColumnToMethodMatchingStrategy}.
869+ *
870+ * @param strategy - matching strategy
871+ * @return same instance of the builder
872+ */
873+ public Builder columnToMethodMatchingStrategy (ColumnToMethodMatchingStrategy strategy ) {
874+ this .columnToMethodMatchingStrategy = strategy ;
875+ return this ;
876+ }
877+
849878 public Client build () {
850879 setDefaults ();
851880
@@ -854,12 +883,24 @@ public Client build() {
854883 throw new IllegalArgumentException ("At least one endpoint is required" );
855884 }
856885 // check if username and password are empty. so can not initiate client?
857- if (!this .configuration .containsKey ("access_token" ) && (!this .configuration .containsKey ("user" ) || !this .configuration .containsKey ("password" ))) {
858- throw new IllegalArgumentException ("Username and password are required" );
886+ if (!this .configuration .containsKey ("access_token" ) &&
887+ (!this .configuration .containsKey ("user" ) || !this .configuration .containsKey ("password" )) &&
888+ !MapUtils .getFlag (this .configuration , "ssl_authentication" )) {
889+ throw new IllegalArgumentException ("Username and password (or access token, or SSL authentication) are required" );
859890 }
860891
861- if (this .configuration .containsKey (ClickHouseClientOption .TRUST_STORE ) &&
862- this .configuration .containsKey (ClickHouseClientOption .SSL_CERTIFICATE )) {
892+ if (this .configuration .containsKey ("ssl_authentication" ) &&
893+ (this .configuration .containsKey ("password" ) || this .configuration .containsKey ("access_token" ))) {
894+ throw new IllegalArgumentException ("Only one of password, access token or SSL authentication can be used per client." );
895+ }
896+
897+ if (this .configuration .containsKey ("ssl_authentication" ) &&
898+ !this .configuration .containsKey (ClickHouseClientOption .SSL_CERTIFICATE .getKey ())) {
899+ throw new IllegalArgumentException ("SSL authentication requires a client certificate" );
900+ }
901+
902+ if (this .configuration .containsKey (ClickHouseClientOption .TRUST_STORE .getKey ()) &&
903+ this .configuration .containsKey (ClickHouseClientOption .SSL_CERTIFICATE .getKey ())) {
863904 throw new IllegalArgumentException ("Trust store and certificates cannot be used together" );
864905 }
865906
@@ -891,7 +932,7 @@ public Client build() {
891932 throw new IllegalArgumentException ("Nor server timezone nor specific timezone is set" );
892933 }
893934
894- return new Client (this .endpoints , this .configuration , this .useNewImplementation , this .sharedOperationExecutor );
935+ return new Client (this .endpoints , this .configuration , this .useNewImplementation , this .sharedOperationExecutor , this . columnToMethodMatchingStrategy );
895936 }
896937
897938 private static final int DEFAULT_NETWORK_BUFFER_SIZE = 300_000 ;
@@ -963,6 +1004,10 @@ private void setDefaults() {
9631004 if (!configuration .containsKey ("client_allow_binary_reader_to_reuse_buffers" )) {
9641005 allowBinaryReaderToReuseBuffers (false );
9651006 }
1007+
1008+ if (columnToMethodMatchingStrategy == null ) {
1009+ columnToMethodMatchingStrategy = DefaultColumnToMethodMatchingStrategy .INSTANCE ;
1010+ }
9661011 }
9671012 }
9681013
@@ -1018,19 +1063,17 @@ public synchronized void register(Class<?> clazz, TableSchema schema) {
10181063 }
10191064 tableSchemaCache .put (schemaKey , schema );
10201065
1066+ ColumnToMethodMatchingStrategy matchingStrategy = columnToMethodMatchingStrategy ;
1067+
10211068 //Create a new POJOSerializer with static .serialize(object, columns) methods
10221069 Map <String , Method > classGetters = new HashMap <>();
10231070 Map <String , Method > classSetters = new HashMap <>();
10241071 for (Method method : clazz .getMethods ()) {//Clean up the method names
1025- String methodName = method .getName ();
1026- if (methodName .startsWith ("get" ) || methodName .startsWith ("has" )) {
1027- methodName = methodName .substring (3 ).toLowerCase ();
1028- classGetters .put (methodName , method );
1029- } else if (methodName .startsWith ("is" )) {
1030- methodName = methodName .substring (2 ).toLowerCase ();
1072+ if (matchingStrategy .isGetter (method .getName ())) {
1073+ String methodName = matchingStrategy .normalizeMethodName (method .getName ());
10311074 classGetters .put (methodName , method );
1032- } else if (methodName . startsWith ( "set" )) {
1033- methodName = methodName . substring ( 3 ). toLowerCase ( );
1075+ } else if (matchingStrategy . isSetter ( method . getName () )) {
1076+ String methodName = matchingStrategy . normalizeMethodName ( method . getName () );
10341077 classSetters .put (methodName , method );
10351078 }
10361079 }
@@ -1040,7 +1083,7 @@ public synchronized void register(Class<?> clazz, TableSchema schema) {
10401083 boolean defaultsSupport = schema .hasDefaults ();
10411084 tableSchemaHasDefaults .put (schemaKey , defaultsSupport );
10421085 for (ClickHouseColumn column : schema .getColumns ()) {
1043- String propertyName = column .getColumnName (). toLowerCase (). replace ( "_" , "" ). replace ( "." , "" );
1086+ String propertyName = columnToMethodMatchingStrategy . normalizeColumnName ( column .getColumnName ());
10441087 Method getterMethod = classGetters .get (propertyName );
10451088 if (getterMethod != null ) {
10461089 schemaSerializers .put (column .getColumnName (), (obj , stream ) -> {
0 commit comments