@@ -49,16 +49,36 @@ public class DatabaseClientPropertySource {
4949
5050 static {
5151 connectionPropertyHandlers = new LinkedHashMap <>();
52- connectionPropertyHandlers .put (PREFIX + "host" , (bean , value ) -> bean .setHost ((String ) value ));
52+ connectionPropertyHandlers .put (PREFIX + "host" , (bean , value ) -> {
53+ if (value instanceof String ) {
54+ bean .setHost ((String ) value );
55+ } else {
56+ throw new IllegalArgumentException ("Host must be of type String" );
57+ }
58+ });
5359 connectionPropertyHandlers .put (PREFIX + "port" , (bean , value ) -> {
5460 if (value instanceof String ) {
5561 bean .setPort (Integer .parseInt ((String ) value ));
56- } else {
62+ } else if ( value instanceof Integer ) {
5763 bean .setPort ((int ) value );
64+ } else {
65+ throw new IllegalArgumentException ("Port must be of type String or Integer" );
66+ }
67+ });
68+ connectionPropertyHandlers .put (PREFIX + "database" , (bean , value ) -> {
69+ if (value instanceof String ) {
70+ bean .setDatabase ((String ) value );
71+ } else {
72+ throw new IllegalArgumentException ("Database must be of type String" );
73+ }
74+ });
75+ connectionPropertyHandlers .put (PREFIX + "basePath" , (bean , value ) -> {
76+ if (value instanceof String ) {
77+ bean .setBasePath ((String ) value );
78+ } else {
79+ throw new IllegalArgumentException ("Base path must be of type String" );
5880 }
5981 });
60- connectionPropertyHandlers .put (PREFIX + "database" , (bean , value ) -> bean .setDatabase ((String ) value ));
61- connectionPropertyHandlers .put (PREFIX + "basePath" , (bean , value ) -> bean .setBasePath ((String ) value ));
6282 connectionPropertyHandlers .put (PREFIX + "connectionType" , (bean , value ) -> {
6383 if (value instanceof DatabaseClient .ConnectionType ) {
6484 bean .setConnectionType ((DatabaseClient .ConnectionType ) value );
@@ -67,15 +87,19 @@ public class DatabaseClientPropertySource {
6787 if (val .trim ().length () > 0 ) {
6888 bean .setConnectionType (DatabaseClient .ConnectionType .valueOf (val .toUpperCase ()));
6989 }
70- } else
90+ } else {
7191 throw new IllegalArgumentException ("Connection type must either be a String or an instance of ConnectionType" );
92+ }
7293 });
7394 }
7495
7596 public DatabaseClientPropertySource (Function <String , Object > propertySource ) {
7697 this .propertySource = propertySource ;
7798 }
7899
100+ /**
101+ * @return an instance of {@code DatabaseClient} based on the given property source
102+ */
79103 public DatabaseClient newClient () {
80104 DatabaseClientFactory .Bean bean = newClientBean ();
81105 // For consistency with how clients have been created - i.e. not via a Bean class, but via
@@ -85,9 +109,12 @@ public DatabaseClient newClient() {
85109 // (and this behavior is expected by some existing tests).
86110 return DatabaseClientFactory .newClient (bean .getHost (), bean .getPort (), bean .getBasePath (), bean .getDatabase (),
87111 bean .getSecurityContext (), bean .getConnectionType ());
88-
89112 }
90113
114+ /**
115+ * @return an instance of {@code DatabaseClientFactory.Bean} based on the given property source. This is primarily
116+ * intended for testing purposes so that the Bean can be verified without creating a client.
117+ */
91118 public DatabaseClientFactory .Bean newClientBean () {
92119 final DatabaseClientFactory .Bean bean = new DatabaseClientFactory .Bean ();
93120 connectionPropertyHandlers .forEach ((propName , consumer ) -> {
@@ -101,20 +128,28 @@ public DatabaseClientFactory.Bean newClientBean() {
101128 }
102129
103130 private DatabaseClientFactory .SecurityContext newSecurityContext () {
104- DatabaseClientFactory .SecurityContext securityContext = (DatabaseClientFactory .SecurityContext )
105- propertySource .apply (PREFIX + "securityContext" );
106- if (securityContext != null ) {
107- return securityContext ;
131+ Object securityContextValue = propertySource .apply (PREFIX + "securityContext" );
132+ if (securityContextValue != null ) {
133+ if (securityContextValue instanceof DatabaseClientFactory .SecurityContext ) {
134+ return (DatabaseClientFactory .SecurityContext ) securityContextValue ;
135+ }
136+ throw new IllegalArgumentException ("Security context must be of type " + DatabaseClientFactory .SecurityContext .class .getName ());
108137 }
109138
110- String type = ( String ) propertySource .apply (PREFIX + "securityContextType" );
111- if (type == null || type . trim (). length () == 0 ) {
112- throw new IllegalArgumentException ("Must define a security context or security context type" );
139+ Object typeValue = propertySource .apply (PREFIX + "securityContextType" );
140+ if (typeValue == null || !( typeValue instanceof String ) ) {
141+ throw new IllegalArgumentException ("Security context should be set, or security context type must be of type String " );
113142 }
114- securityContext = newSecurityContext (type );
143+ final String securityContextType = (String )typeValue ;
144+ final SSLInputs sslInputs = buildSSLInputs (securityContextType );
145+
146+ DatabaseClientFactory .SecurityContext securityContext = newSecurityContext (securityContextType , sslInputs );
147+
148+ X509TrustManager trustManager = determineTrustManager (sslInputs );
149+ SSLContext sslContext = sslInputs .getSslContext () != null ?
150+ sslInputs .getSslContext () :
151+ determineSSLContext (sslInputs , trustManager );
115152
116- X509TrustManager trustManager = determineTrustManager ();
117- SSLContext sslContext = determineSSLContext (trustManager );
118153 if (sslContext != null ) {
119154 securityContext .withSSLContext (sslContext , trustManager );
120155 }
@@ -123,7 +158,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() {
123158 return securityContext ;
124159 }
125160
126- private DatabaseClientFactory .SecurityContext newSecurityContext (String type ) {
161+ private DatabaseClientFactory .SecurityContext newSecurityContext (String type , SSLInputs sslInputs ) {
127162 switch (type .toLowerCase ()) {
128163 case DatabaseClientBuilder .SECURITY_CONTEXT_TYPE_BASIC :
129164 return newBasicAuthContext ();
@@ -134,64 +169,68 @@ private DatabaseClientFactory.SecurityContext newSecurityContext(String type) {
134169 case DatabaseClientBuilder .SECURITY_CONTEXT_TYPE_KERBEROS :
135170 return newKerberosAuthContext ();
136171 case DatabaseClientBuilder .SECURITY_CONTEXT_TYPE_CERTIFICATE :
137- return newCertificateAuthContext ();
172+ return newCertificateAuthContext (sslInputs );
138173 case DatabaseClientBuilder .SECURITY_CONTEXT_TYPE_SAML :
139174 return newSAMLAuthContext ();
140175 default :
141176 throw new IllegalArgumentException ("Unrecognized security context type: " + type );
142177 }
143178 }
144179
180+ private String getRequiredStringValue (String propertyName ) {
181+ Object value = propertySource .apply (PREFIX + propertyName );
182+ if (value == null || !(value instanceof String )) {
183+ throw new IllegalArgumentException (propertyName + " must be of type String" );
184+ }
185+ return (String ) value ;
186+ }
187+
188+ private String getNullableStringValue (String propertyName ) {
189+ Object value = propertySource .apply (PREFIX + propertyName );
190+ if (value != null && !(value instanceof String )) {
191+ throw new IllegalArgumentException (propertyName + " must be of type String" );
192+ }
193+ return (String )value ;
194+ }
195+
145196 private DatabaseClientFactory .SecurityContext newBasicAuthContext () {
146197 return new DatabaseClientFactory .BasicAuthContext (
147- (String ) propertySource .apply (PREFIX + "username" ),
148- (String ) propertySource .apply (PREFIX + "password" )
198+ getRequiredStringValue ("username" ), getRequiredStringValue ("password" )
149199 );
150200 }
151201
152202 private DatabaseClientFactory .SecurityContext newDigestAuthContext () {
153203 return new DatabaseClientFactory .DigestAuthContext (
154- (String ) propertySource .apply (PREFIX + "username" ),
155- (String ) propertySource .apply (PREFIX + "password" )
204+ getRequiredStringValue ("username" ), getRequiredStringValue ("password" )
156205 );
157206 }
158207
159208 private DatabaseClientFactory .SecurityContext newCloudAuthContext () {
160- return new DatabaseClientFactory .MarkLogicCloudAuthContext (
161- (String ) propertySource .apply (PREFIX + "cloud.apiKey" )
162- );
209+ return new DatabaseClientFactory .MarkLogicCloudAuthContext (getRequiredStringValue ("cloud.apiKey" ));
163210 }
164211
165- private DatabaseClientFactory .SecurityContext newCertificateAuthContext () {
212+ private DatabaseClientFactory .SecurityContext newCertificateAuthContext (SSLInputs sslInputs ) {
166213 try {
167214 return new DatabaseClientFactory .CertificateAuthContext (
168- ( String ) propertySource . apply ( PREFIX + "certificate.file" ),
169- ( String ) propertySource . apply ( PREFIX + "certificate.password" ),
170- determineTrustManager ()
215+ getRequiredStringValue ( "certificate.file" ),
216+ getRequiredStringValue ( "certificate.password" ),
217+ sslInputs . getTrustManager ()
171218 );
172219 } catch (Exception e ) {
173220 throw new RuntimeException ("Unable to create CertificateAuthContext; cause " + e .getMessage (), e );
174221 }
175222 }
176223
177224 private DatabaseClientFactory .SecurityContext newKerberosAuthContext () {
178- return new DatabaseClientFactory .KerberosAuthContext (
179- (String ) propertySource .apply (PREFIX + "kerberos.principal" )
180- );
225+ return new DatabaseClientFactory .KerberosAuthContext (getRequiredStringValue ("kerberos.principal" ));
181226 }
182227
183228 private DatabaseClientFactory .SecurityContext newSAMLAuthContext () {
184- return new DatabaseClientFactory .SAMLAuthContext (
185- (String ) propertySource .apply (PREFIX + "saml.token" )
186- );
229+ return new DatabaseClientFactory .SAMLAuthContext (getRequiredStringValue ("saml.token" ));
187230 }
188231
189- private SSLContext determineSSLContext (X509TrustManager trustManager ) {
190- SSLContext sslContext = (SSLContext ) propertySource .apply (PREFIX + "sslContext" );
191- if (sslContext != null ) {
192- return sslContext ;
193- }
194- String protocol = (String ) propertySource .apply (PREFIX + "sslProtocol" );
232+ private SSLContext determineSSLContext (SSLInputs sslInputs , X509TrustManager trustManager ) {
233+ String protocol = sslInputs .getSslProtocol ();
195234 if (protocol != null ) {
196235 if ("default" .equalsIgnoreCase (protocol )) {
197236 try {
@@ -200,6 +239,8 @@ private SSLContext determineSSLContext(X509TrustManager trustManager) {
200239 throw new RuntimeException ("Unable to obtain default SSLContext; cause: " + e .getMessage (), e );
201240 }
202241 }
242+
243+ SSLContext sslContext ;
203244 try {
204245 sslContext = SSLContext .getInstance (protocol );
205246 } catch (NoSuchAlgorithmException e ) {
@@ -220,20 +261,15 @@ private SSLContext determineSSLContext(X509TrustManager trustManager) {
220261 return null ;
221262 }
222263
223- private X509TrustManager determineTrustManager () {
224- Object trustManagerObject = propertySource .apply (PREFIX + "trustManager" );
225- if (trustManagerObject != null ) {
226- if (trustManagerObject instanceof X509TrustManager ) {
227- return (X509TrustManager ) trustManagerObject ;
228- }
229- throw new IllegalArgumentException (
230- String .format ("Trust manager must be an instance of %s" , X509TrustManager .class .getName ()));
264+ private X509TrustManager determineTrustManager (SSLInputs sslInputs ) {
265+ if (sslInputs .getTrustManager () != null ) {
266+ return sslInputs .getTrustManager ();
231267 }
232268 // If the user chooses the "default" SSLContext, then it's already been initialized - but OkHttp still
233269 // needs a separate X509TrustManager, so use the JVM's default trust manager. The assumption is that the
234270 // default SSLContext was initialized with the JVM's default trust manager. A user can of course always override
235271 // this by simply providing their own trust manager.
236- if ("default" .equalsIgnoreCase (( String ) propertySource . apply ( PREFIX + "sslProtocol" ))) {
272+ if ("default" .equalsIgnoreCase (sslInputs . getSslProtocol ( ))) {
237273 X509TrustManager defaultTrustManager = SSLUtil .getDefaultTrustManager ();
238274 if (logger .isDebugEnabled () && defaultTrustManager != null && defaultTrustManager .getAcceptedIssuers () != null ) {
239275 logger .debug ("Count of accepted issuers in default trust manager: {}" ,
@@ -261,4 +297,69 @@ private DatabaseClientFactory.SSLHostnameVerifier determineHostnameVerifier() {
261297 }
262298 return null ;
263299 }
300+
301+ /**
302+ * Uses the given propertySource to construct the inputs pertaining to constructing an SSLContext and an
303+ * X509TrustManager.
304+ *
305+ * @param securityContextType used for applying "default" as the SSL protocol for MarkLogic cloud authentication in
306+ * case the user does not define their own SSLContext or SSL protocol
307+ * @return
308+ */
309+ private SSLInputs buildSSLInputs (String securityContextType ) {
310+ SSLContext sslContext = null ;
311+ Object val = propertySource .apply (PREFIX + "sslContext" );
312+ if (val != null ) {
313+ if (val instanceof SSLContext ) {
314+ sslContext = (SSLContext ) val ;
315+ } else {
316+ throw new IllegalArgumentException ("SSL context must be an instanceof " + SSLContext .class .getName ());
317+ }
318+ }
319+
320+ String sslProtocol = getNullableStringValue ("sslProtocol" );
321+ if (sslContext == null &&
322+ (sslProtocol == null || sslProtocol .trim ().length () == 0 ) &&
323+ DatabaseClientBuilder .SECURITY_CONTEXT_TYPE_MARKLOGIC_CLOUD .equalsIgnoreCase (securityContextType )) {
324+ sslProtocol = "default" ;
325+ }
326+
327+ val = propertySource .apply (PREFIX + "trustManager" );
328+ X509TrustManager trustManager = null ;
329+ if (val != null ) {
330+ if (val instanceof X509TrustManager ) {
331+ trustManager = (X509TrustManager ) val ;
332+ } else {
333+ throw new IllegalArgumentException ("Trust manager must be an instanceof " + X509TrustManager .class .getName ());
334+ }
335+ }
336+ return new SSLInputs (sslContext , sslProtocol , trustManager );
337+ }
338+
339+ /**
340+ * Captures the inputs provided by the caller that pertain to constructing an SSLContext.
341+ */
342+ private static class SSLInputs {
343+ private final SSLContext sslContext ;
344+ private final String sslProtocol ;
345+ private final X509TrustManager trustManager ;
346+
347+ public SSLInputs (SSLContext sslContext , String sslProtocol , X509TrustManager trustManager ) {
348+ this .sslContext = sslContext ;
349+ this .sslProtocol = sslProtocol ;
350+ this .trustManager = trustManager ;
351+ }
352+
353+ public SSLContext getSslContext () {
354+ return sslContext ;
355+ }
356+
357+ public String getSslProtocol () {
358+ return sslProtocol ;
359+ }
360+
361+ public X509TrustManager getTrustManager () {
362+ return trustManager ;
363+ }
364+ }
264365}
0 commit comments