44import com .johnlpage .memex .util .MongoSchemaGenerator ;
55import com .mongodb .client .MongoCollection ;
66import com .mongodb .client .MongoDatabase ;
7+ import com .mongodb .client .model .CreateCollectionOptions ;
8+ import com .mongodb .client .model .ValidationAction ;
9+ import com .mongodb .client .model .ValidationLevel ;
10+ import com .mongodb .client .model .ValidationOptions ;
711import java .util .*;
8-
9- import org .apache .kafka .common .protocol .types .Field ;
1012import org .bson .Document ;
11- import org .bson .json .JsonWriterSettings ;
1213import org .slf4j .Logger ;
1314import org .slf4j .LoggerFactory ;
1415import org .springframework .beans .factory .annotation .Value ;
2324/**
2425 * This is an example of a class you can use to ensure the system is ready to start correctly. It
2526 * shows you how to check or indexes etc. And optionally create them - although spring has similar
26- * lifecycle methods they aren't comprehensive enough. It uses ApplicationRunner to ensure this is
27+ * lifecycle methods, they aren't comprehensive enough. It uses ApplicationRunner to ensure this is
2728 * run on startup.
2829 */
2930@ Service
@@ -60,15 +61,24 @@ public class MongoDbPreflightCheckService {
6061 @ Value ("${memex.preflight.createSearchIndexes:true}" )
6162 private boolean createSearchIndexes ;
6263
64+ @ Value ("${memex.preflight.enforceSchema:true}" )
65+ private boolean enforceSchema ;
66+
6367 @ Value ("${memex.mongodb.hasSearch:true}" )
6468 private boolean hasSearch ;
6569
70+ @ Value ("${memex.preflight.schemaOnCreate:true}" )
71+ private boolean schemaOnCreate ;
72+
73+ @ Value ("${memex.preflight.dropAllCollections:false}" )
74+ private boolean dropAllCollections ;
75+
6676 public MongoDbPreflightCheckService (ApplicationContext context , MongoTemplate mongoTemplate ) {
6777 this .context = context ;
6878 this .mongoTemplate = mongoTemplate ;
6979 }
7080
71- /** Ensure all Collections exist, create them or quit depending on flag . */
81+ /** Ensure all Collections exist, create them or quit depending on flags . */
7282 List <Document > ensureCollectionsExist (Document schemaAndIndexes ) {
7383 MongoDatabase database = mongoTemplate .getDb ();
7484
@@ -78,40 +88,76 @@ List<Document> ensureCollectionsExist(Document schemaAndIndexes) {
7888 for (Document requiredCollection : requiredCollections ) {
7989 String collectionName = requiredCollection .getString ("name" );
8090 String className = requiredCollection .getString ("serverSchemaEnforcement" );
91+ Document validator = null ;
92+ if (className != null ) {
93+ try {
94+ Class <?> clazz = Class .forName (className );
95+ validator = MongoSchemaGenerator .generateSchema (clazz );
96+ LOG .info ("Scheme for {} = '{}' " , collectionName , validator .toJson ());
97+
98+ } catch (Exception e ) {
99+ LOG .error ("Could not process class {}: {}" , className , e .getMessage ());
100+ }
101+ }
102+
103+ // Rarely used but useful if you have no easy way to drop the DB
104+ if (dropAllCollections && existingCollections .contains (collectionName )) {
105+ LOG .warn (
106+ "WARNING: FORCE DROPPING COLLECTION {} !!! - disable in app config if you don't want to" );
107+ mongoTemplate .dropCollection (collectionName );
108+ existingCollections .remove (collectionName );
109+ }
81110
82111 if (!existingCollections .contains (collectionName )) {
83112 if (createCollections ) {
84- LOG .warn ("Collection '{}' does not exist, creating it." , collectionName );
85- database .createCollection (collectionName );
113+
114+ if (schemaOnCreate ) {
115+ /*
116+ This Code is here if you cannot apply validation using collMod you can do so on
117+ create - but to change it you would need to drop and recreate the collection, which
118+ may be OK in Development
119+ */
120+
121+ ValidationOptions validationOptions =
122+ new ValidationOptions ()
123+ .validator (validator )
124+ .validationLevel (ValidationLevel .MODERATE ) // or STRICT, OFF
125+ .validationAction (ValidationAction .ERROR ); // or WARN
126+ CreateCollectionOptions options =
127+ new CreateCollectionOptions ().validationOptions (validationOptions );
128+ database .createCollection (collectionName , options );
129+
130+ LOG .warn (
131+ "Collection '{}' does not exist, creating it with schema validation." ,
132+ collectionName );
133+ } else {
134+ LOG .warn ("Collection '{}' does not exist, creating it." , collectionName );
135+ database .createCollection (collectionName );
136+ }
137+
86138 } else {
87139 LOG .error ("Collection '{}' does not exist, cancelling startup" , collectionName );
88140 int exitCode = SpringApplication .exit (context , () -> 0 );
89141 System .exit (exitCode );
90142 }
91143 }
92144
93- if (className != null ) {
145+ if (validator != null && enforceSchema && ! schemaOnCreate ) {
94146 try {
95- Class <?> clazz = Class .forName (className );
96- Document schema = MongoSchemaGenerator .generateSchema (clazz );
97- LOG .info ("Schema: {}" , schema .toJson ());
98-
99147 // Apply validator via collMod
100148 Document collModCmd =
101149 new Document ("collMod" , collectionName )
102- .append ("validator" , schema )
150+ .append ("validator" , validator )
103151 .append (
104152 "validationLevel" , "moderate" ) // doesn't retroactively reject existing docs
105153 .append ("validationAction" , "error" ); // reject bad inserts/updates
106154
107155 database .runCommand (collModCmd );
108- LOG .info ("Enforcing Schema based on {}" , className );
109- LOG .debug (
110- "Collection '{}' schema enforced" ,
111- schema .toJson (JsonWriterSettings .builder ().indent (true ).build ()));
156+ LOG .info ("Enforcing Schema Validation with collMod based on {}" , className );
112157
113- } catch (ClassNotFoundException e ) {
114- LOG .error ("Could not load class {}: {}" , className , e .getMessage ());
158+ } catch (Exception e ) {
159+ LOG .error (
160+ "Error enforcing schema validation for class {}: {}" , className , e .getMessage ());
115161 }
116162 }
117163 }
@@ -160,6 +206,7 @@ void ensureRequiredSearchIndexesExist(List<Document> requiredInfo) {
160206 if (!hasSearch ) {
161207 return ;
162208 }
209+
163210 for (Document requiredCollection : requiredInfo ) {
164211 String collectionName = requiredCollection .getString ("name" );
165212
@@ -169,17 +216,24 @@ void ensureRequiredSearchIndexesExist(List<Document> requiredInfo) {
169216 if (requiredSearchIndexes != null ) {
170217
171218 // Get a list of the indexes that are defined and their statuses
219+ AggregationResults <Document > results ;
172220 AggregationOperation listIndexes = Aggregation .stage ("{\" $listSearchIndexes\" : {}}" );
173- AggregationResults <Document > results =
174- mongoTemplate .aggregate (
175- Aggregation .newAggregation (listIndexes ), collectionName , Document .class );
221+ try {
222+ results =
223+ mongoTemplate .aggregate (
224+ Aggregation .newAggregation (listIndexes ), collectionName , Document .class );
225+ } catch (Exception e ) {
226+ LOG .error (
227+ "ERROR: memex.mongodb.hasSearch=true but Search not available: {}" , e .getMessage ());
228+ return ;
229+ }
176230 Map <String , Document > resultsearchIndexMap = new HashMap <>();
177231 for (Document existingSearchIndex : results ) {
178232 String key = existingSearchIndex .getString ("name" );
179233 resultsearchIndexMap .put (key , existingSearchIndex );
180234 }
181235
182- // Iterate over the indexes we requires
236+ // Iterate over the indexes we require
183237 for (Document requiredSearchIndex : requiredSearchIndexes ) {
184238 String requiredName = requiredSearchIndex .getString ("name" );
185239 Document requiredDefinition = requiredSearchIndex .get ("definition" , Document .class );
@@ -230,10 +284,15 @@ public ApplicationRunner mongoPreflightCheck(MongoVersionBean mongoVersionBean)
230284 mongoVersionBean .getMinorversion ());
231285 if (createRequiredIndexes ) {
232286 LOG .warn (
233- "!!! MEMEX IS CONFIGURED TO AUTOMATICALLY CREATE MISSING INDEXES - THIS IS NOT RECOMMENDED IN "
287+ "WARNING: MEMEX IS CONFIGURED TO AUTOMATICALLY CREATE MISSING INDEXES - THIS IS NOT RECOMMENDED IN "
234288 + "PRODUCTION !" );
235289 }
236-
290+ LOG .info ("createRequiredIndexes: {}" , createRequiredIndexes );
291+ LOG .info ("createCollections: {}" , createCollections );
292+ LOG .info ("createSearchIndexes: {}" , createSearchIndexes );
293+ LOG .info ("enforceSchema: {}" , enforceSchema );
294+ LOG .info ("hasSearch: {}" , hasSearch );
295+ LOG .info ("schemaOnCreate: {}" , schemaOnCreate );
237296 Document schemaAndIndexes = Document .parse (SCHEMA_AND_INDEXES );
238297 List <Document > requiredInfo = ensureCollectionsExist (schemaAndIndexes );
239298 ensureRequiredIndexesExist (requiredInfo );
0 commit comments