88import java .util .logging .Logger ;
99
1010import picoded .core .conv .GenericConvert ;
11+ import picoded .core .struct .GenericConvertHashMap ;
1112import picoded .core .struct .GenericConvertMap ;
1213import picoded .dstack .core .*;
1314
@@ -47,44 +48,48 @@ public class MongoDBStack extends CoreStack {
4748 //-------------------------------------------------------------------------
4849
4950 /// Default settings option JSON
50- protected static String defaultOptJson = "{" + //
51- " \" w\" :\" majority\" ," + //
52- " \" retryWrites\" :\" true\" ," + //
53- " \" retryReads\" :\" true\" ," + //
54- " \" maxPoolSize\" :25," + //
55- " \" compressors\" :\" zstd\" " + //
56- "}" ;
51+ protected static String defaultOptJson = "{" + //
52+ " \" w\" :\" majority\" ," + //
53+ " \" retryWrites\" :\" true\" ," + //
54+ " \" retryReads\" :\" true\" ," + //
55+ " \" maxPoolSize\" :10," + //
56+ " \" compressors\" :\" zstd\" " + //
57+ "}" ;
58+
5759 // "&readPreference=master&readConcernLevel=majority"
5860 // "&readPreference=nearest&readConcernLevel=linearizable"
59-
60- protected static String mapToOptStr (Map <String ,Object > map ) {
61+
62+ /**
63+ * Map the option object to a URI parameter string
64+ */
65+ protected static String mapToOptStr (Map <String , Object > map ) {
6166 // Get the sorted list of keys
6267 List <String > keys = (new ArrayList <>(map .keySet ()));
6368 Collections .sort (keys );
64-
69+
6570 // The ret stringbuilder
6671 StringBuilder ret = new StringBuilder ();
67-
72+
6873 // Lets loop the keys
69- for (String key : keys ) {
70- if ( ret .length () > 0 ) {
74+ for (String key : keys ) {
75+ if ( ret .length () > 0 ) {
7176 ret .append ("&" );
7277 }
73- ret .append (key + "=" + GenericConvert .toString (map .get (key )));
78+ ret .append (key + "=" + GenericConvert .toString (map .get (key )));
7479 }
75-
80+
7681 // And return the built str
7782 return ret .toString ();
7883 }
79-
84+
8085 //-------------------------------------------------------------------------
81- // Database connection constructor
86+ // Database connection URL constructor
8287 //-------------------------------------------------------------------------
8388
8489 /**
8590 * Given the mongodb config object, get the full_url
8691 */
87- public static String getFullConnectionURL (GenericConvertMap <String , Object > config ) {
92+ public static String getFullConnectionURL_primary (GenericConvertMap <String , Object > config ) {
8893 // Get the DB name (required)
8994 String dbname = config .getString ("name" , null );
9095 if (dbname == null || dbname .isEmpty ()) {
@@ -103,9 +108,10 @@ public static String getFullConnectionURL(GenericConvertMap<String, Object> conf
103108 String pass = config .getString ("pass" , null );
104109 String host = config .getString ("host" , "localhost" );
105110 int port = config .getInt ("port" , 27017 );
106-
111+
107112 // Hanlding of option string
108- GenericConvertMap <String ,Object > optMap = config .getGenericConvertStringMap ("opt" , defaultOptJson );
113+ GenericConvertMap <String , Object > optMap = config .getGenericConvertStringMap ("opt" ,
114+ defaultOptJson );
109115 String optStr = config .getString ("opt_str" , mapToOptStr (optMap ));
110116
111117 // Lets do a logging, for missing read concern if its not configured
@@ -121,10 +127,11 @@ public static String getFullConnectionURL(GenericConvertMap<String, Object> conf
121127 // Unless you know what your doing from a performance standpoint, it is strongly recommended to use
122128 // `readConcernLevel=linearizable`
123129 //
124- LOGGER .warning ("MongoDB is configured without readConcernLevel, "
125- + "this is alright for a single node, but `readConcernLevel=linearizable`"
126- + "or `readPreference=master&readConcernLevel=majority`"
127- + "is highly recommended for replica clusters to ensure read after write consistency." );
130+ LOGGER
131+ .warning ("MongoDB is configured without readConcernLevel for the primary connection, "
132+ + "this is alright for a single node, but `readConcernLevel=linearizable`"
133+ + "or `readPreference=master&readConcernLevel=majority`"
134+ + "is highly recommended for replica clusters to ensure read after write consistency." );
128135 }
129136
130137 // In the future we may want to support opt_map
@@ -145,19 +152,95 @@ public static String getFullConnectionURL(GenericConvertMap<String, Object> conf
145152 }
146153
147154 /**
148- * Given the mongodb config object, get the MongoClient connection
155+ * Given the mongodb config object, get the full_url
149156 */
150- public static MongoClient setupFromConfig (GenericConvertMap <String , Object > inConfig ) {
151- // Get the full_url
152- String full_url = getFullConnectionURL (inConfig );
157+ public static String getFullConnectionURL_secondary (GenericConvertMap <String , Object > config ) {
158+ // Null check for secondary connection
159+ String sec_mode = config .getString ("sec_mode" , null );
160+ if (sec_mode == null ) {
161+ return null ;
162+ }
153163
154- // Lets build using the stable API settings
155- ServerApi serverApi = ServerApi .builder ().version (ServerApiVersion .V1 ).build ();
156- MongoClientSettings settings = MongoClientSettings .builder ()
157- .applyConnectionString (new ConnectionString (full_url )).serverApi (serverApi ).build ();
164+ // Get the DB name (required)
165+ String dbname = config .getString ("name" , null );
166+ if (dbname == null || dbname .isEmpty ()) {
167+ throw new IllegalArgumentException ("Missing database 'name' for mongodb config" );
168+ }
158169
159- // Create the client, and return it
160- return MongoClients .create (settings );
170+ // Get the full connection url, and use it if present
171+ String full_url = config .getString ("full_url" , null );
172+ if (full_url != null ) {
173+ return full_url ;
174+ }
175+
176+ // Lets get the config respectively
177+ String protocol = config .getString ("protocol" , "mongodb" );
178+ String user = config .getString ("user" , null );
179+ String pass = config .getString ("pass" , null );
180+ String host = config .getString ("host" , "localhost" );
181+ int port = config .getInt ("port" , 27017 );
182+
183+ // Hanlding of option string
184+ GenericConvertMap <String , Object > optMap = new GenericConvertHashMap <>();
185+ optMap .putAll (config .getGenericConvertStringMap ("opt" , defaultOptJson ));
186+ optMap .putAll (config .getGenericConvertStringMap ("sec_opt" , "{}" ));
187+
188+ // The opt string overwrite
189+ String optStr = config .getString ("sec_opt_str" , mapToOptStr (optMap ));
190+
191+ // Lets do a logging, for missing read concern if its not configured
192+ if (optStr .indexOf ("readConcernLevel" ) < 0 ) {
193+ //
194+ // readConcernLevel is a complicated topic, do consider reading up
195+ // https://jepsen.io/analyses/mongodb-4.2.6
196+ // https://www.mongodb.com/blog/post/performance-best-practices-transactions-and-read--write-concerns
197+ // https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/#mongodb-readconcern-readconcern.-linearizable-
198+ //
199+ // This option was removed by default, as an error will be thrown when its applied to single node clusters
200+ //
201+ // Unless you know what your doing from a performance standpoint, it is strongly recommended to use
202+ // `readConcernLevel=linearizable`
203+ //
204+ LOGGER
205+ .warning ("MongoDB is configured without readConcernLevel for the secondary connection, "
206+ + "this is alright for a single node, but `readConcernLevel=linearizable`"
207+ + "or `readPreference=master&readConcernLevel=majority`"
208+ + "is highly recommended for replica clusters to ensure read after write consistency." );
209+ }
210+
211+ // In the future we may want to support opt_map
212+ // GenericConvertMap<String,Object> optMap = config.getGenericConvertStringMap("opt_map", "{}");
213+
214+ // Lets build the auth str
215+ String authStr = "" ;
216+ if (user != null && pass != null ) {
217+ authStr = user + ":" + pass + "@" ;
218+ }
219+
220+ // Return the full URL depending on the settings
221+ if (protocol .equals ("mongodb+srv" )) {
222+ // mongodb+srv does not support the port protocol
223+ return protocol + "://" + authStr + host + "/" + dbname + "?" + optStr ;
224+ }
225+ return protocol + "://" + authStr + host + ":" + port + "/" + dbname + "?" + optStr ;
226+ }
227+
228+ //-------------------------------------------------------------------------
229+ // Database connection setup
230+ //-------------------------------------------------------------------------
231+
232+ /**
233+ * Utility library, used to check that the connection "works"
234+ * @param db_conn
235+ */
236+ protected void checkMongoDatabaseConnection (MongoDatabase db_conn ) {
237+
238+ // Safety check, get the list of connection names
239+ // this should throw an error if its not connected
240+ Iterable <String > list = db_conn .listCollectionNames ();
241+ for (String n : list ) {
242+ // does nothing
243+ }
161244 }
162245
163246 /**
@@ -175,11 +258,47 @@ public MongoDBStack(GenericConvertMap<String, Object> inConfig) {
175258 "Missing 'mongodb' config object for MongoDB stack provider" );
176259 }
177260
261+ // Primary connection
262+ // ------
263+
264+ // Get the full_url
265+ String full_url = getFullConnectionURL_primary (inConfig );
266+
267+ // Lets build using the stable API settings
268+ ServerApi serverApi = ServerApi .builder ().version (ServerApiVersion .V1 ).build ();
269+ MongoClientSettings settings = MongoClientSettings .builder ()
270+ .applyConnectionString (new ConnectionString (full_url )).serverApi (serverApi ).build ();
271+
178272 // Get the connection & database
179- client_conn = setupFromConfig ( dbConfig );
273+ client_conn = MongoClients . create ( settings );
180274
181- // Get the DB connection
275+ // Get the DB connection, and validate it
182276 db_conn = client_conn .getDatabase (dbConfig .fetchString ("name" ));
277+ checkMongoDatabaseConnection (db_conn );
278+
279+ // Secondary connection
280+ // ------
281+
282+ // Null check for secondary connection
283+ String config_sec_mode = config .getString ("sec_mode" , null );
284+ if (config_sec_mode == null ) {
285+ return ;
286+ }
287+ sec_mode = config_sec_mode .trim ().toUpperCase ();
288+
289+ // lets get the secondary connection
290+ full_url = getFullConnectionURL_secondary (inConfig );
291+ serverApi = ServerApi .builder ().version (ServerApiVersion .V1 ).build ();
292+ settings = MongoClientSettings .builder ()
293+ .applyConnectionString (new ConnectionString (full_url )).serverApi (serverApi ).build ();
294+
295+ // Get the connection & database
296+ client_conn = MongoClients .create (settings );
297+
298+ // Get the DB connection, and validate it
299+ sec_db_conn = client_conn .getDatabase (dbConfig .fetchString ("name" ));
300+ checkMongoDatabaseConnection (sec_db_conn );
301+
183302 }
184303
185304 /**
0 commit comments