1818import java .net .UnknownHostException ;
1919import java .util .Arrays ;
2020import java .util .Collection ;
21+ import java .util .Collections ;
2122import java .util .HashSet ;
2223import java .util .Set ;
2324
2728import org .slf4j .Logger ;
2829import org .slf4j .LoggerFactory ;
2930import org .springframework .util .CollectionUtils ;
31+ import org .springframework .util .StringUtils ;
3032
3133import com .mongodb .DB ;
34+ import com .mongodb .DBCollection ;
3235import com .mongodb .MongoClient ;
3336
3437/**
38+ * {@link CleanMongoDB} is a junit {@link TestRule} implementation to be used as for wiping data from MongoDB instance.
39+ * MongoDB specific system databases like {@literal admin} and {@literal local} remain untouched. The rule will apply
40+ * <strong>after</strong> the base {@link Statement}. <br />
41+ * Use as {@link org.junit.ClassRule} to wipe data after finishing all tests within a class or as {@link org.junit.Rule}
42+ * to do so after each {@link org.junit.Test}.
43+ *
3544 * @author Christoph Strobl
45+ * @since 1.6
3646 */
3747public class CleanMongoDB implements TestRule {
3848
3949 private static final Logger LOGGER = LoggerFactory .getLogger (CleanMongoDB .class );
4050
41- public enum Types {
51+ /**
52+ * Defines contents of MongoDB.
53+ */
54+ public enum Struct {
4255 DATABASE , COLLECTION , INDEX ;
4356 }
4457
58+ @ SuppressWarnings ("serial" )//
4559 private Set <String > preserveDatabases = new HashSet <String >() {
46-
47- private static final long serialVersionUID = -8698807376808700046L ;
48-
4960 {
5061 add ("admin" );
5162 add ("local" );
@@ -54,57 +65,278 @@ public enum Types {
5465
5566 private Set <String > dbNames = new HashSet <String >();
5667 private Set <String > collectionNames = new HashSet <String >();
57- private Set <Types > types = new HashSet <CleanMongoDB .Types >();
68+ private Set <Struct > types = new HashSet <CleanMongoDB .Struct >();
5869 private MongoClient client ;
5970
71+ /**
72+ * Create new instance using an internal {@link MongoClient}.
73+ */
6074 public CleanMongoDB () {
6175 this (null );
6276 }
6377
78+ /**
79+ * Create new instance using an internal {@link MongoClient} connecting to specified instance running at host:port.
80+ *
81+ * @param host
82+ * @param port
83+ * @throws UnknownHostException
84+ */
6485 public CleanMongoDB (String host , int port ) throws UnknownHostException {
6586 this (new MongoClient (host , port ));
6687 }
6788
89+ /**
90+ * Create new instance using the given client.
91+ *
92+ * @param client
93+ */
6894 public CleanMongoDB (MongoClient client ) {
6995 this .client = client ;
7096 }
7197
98+ /**
99+ * Removes everything by dropping every single {@link DB}.
100+ *
101+ * @return
102+ */
72103 public static CleanMongoDB everything () {
73104
74105 CleanMongoDB cleanMongoDB = new CleanMongoDB ();
75- cleanMongoDB .clean (Types .DATABASE );
106+ cleanMongoDB .clean (Struct .DATABASE );
76107 return cleanMongoDB ;
77108 }
78109
110+ /**
111+ * Removes everything from the databases with given name by dropping the according {@link DB}.
112+ *
113+ * @param dbNames
114+ * @return
115+ */
79116 public static CleanMongoDB databases (String ... dbNames ) {
80117
81118 CleanMongoDB cleanMongoDB = new CleanMongoDB ();
82- cleanMongoDB .clean (Types .DATABASE );
83- cleanMongoDB .collectionNames .addAll (Arrays .asList (dbNames ));
119+ cleanMongoDB .clean (Struct .DATABASE );
120+ cleanMongoDB .useDatabases (dbNames );
121+ return cleanMongoDB ;
122+ }
123+
124+ /**
125+ * Drops the {@link DBCollection} with given names from every single {@link DB} containing them.
126+ *
127+ * @param collectionNames
128+ * @return
129+ */
130+ public static CleanMongoDB collections (String ... collectionNames ) {
131+ return collections ("" , Arrays .asList (collectionNames ));
132+ }
133+
134+ /**
135+ * Drops the {@link DBCollection} with given names from the named {@link DB}.
136+ *
137+ * @param dbName
138+ * @param collectionNames
139+ * @return
140+ */
141+ public static CleanMongoDB collections (String dbName , Collection <String > collectionNames ) {
142+
143+ CleanMongoDB cleanMongoDB = new CleanMongoDB ();
144+ cleanMongoDB .clean (Struct .COLLECTION );
145+ cleanMongoDB .useCollections (dbName , collectionNames );
84146 return cleanMongoDB ;
85147 }
86148
149+ /**
150+ * Drops all index structures from every single {@link DBCollection}.
151+ *
152+ * @return
153+ */
87154 public static CleanMongoDB indexes () {
155+ return indexes (Collections .<String > emptySet ());
156+ }
157+
158+ /**
159+ * Drops all index structures from every single {@link DBCollection}.
160+ *
161+ * @param collectionNames
162+ * @return
163+ */
164+ public static CleanMongoDB indexes (Collection <String > collectionNames ) {
88165
89166 CleanMongoDB cleanMongoDB = new CleanMongoDB ();
90- cleanMongoDB .clean (Types .INDEX );
167+ cleanMongoDB .clean (Struct .INDEX );
168+ cleanMongoDB .useCollections (collectionNames );
91169 return cleanMongoDB ;
92170 }
93171
94- public CleanMongoDB clean (Types ... types ) {
172+ /**
173+ * Define {@link Struct} to be cleaned.
174+ *
175+ * @param types
176+ * @return
177+ */
178+ public CleanMongoDB clean (Struct ... types ) {
95179
96180 this .types .addAll (Arrays .asList (types ));
97181 return this ;
98182 }
99183
100- public Statement apply () {
184+ /**
185+ * Defines the {@link DB}s to be used. <br />
186+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
187+ * <ul>
188+ * <li>{@link Struct#DATABASE}: Forces drop of named databases.</li>
189+ * <li>{@link Struct#COLLECTION}: Forces drop of collections within named databases.</li>
190+ * <li>{@link Struct#INDEX}: Removes index within collections of named databases.</li>
191+ * </ul>
192+ *
193+ * @param dbNames
194+ * @return
195+ */
196+ public CleanMongoDB useDatabases (String ... dbNames ) {
197+
198+ this .dbNames .addAll (Arrays .asList (dbNames ));
199+ return this ;
200+ }
201+
202+ /**
203+ * Excludes the given {@link DB}s from being processed.
204+ *
205+ * @param dbNames
206+ * @return
207+ */
208+ public CleanMongoDB preserveDatabases (String ... dbNames ) {
209+ this .preserveDatabases .addAll (Arrays .asList (dbNames ));
210+ return this ;
211+ }
212+
213+ /**
214+ * Defines the {@link DBCollection}s to be used. <br />
215+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
216+ * <ul>
217+ * <li>{@link Struct#COLLECTION}: Forces drop of named collections.</li>
218+ * <li>{@link Struct#INDEX}: Removes index within named collections.</li>
219+ * </ul>
220+ *
221+ * @param collectionNames
222+ * @return
223+ */
224+ public CleanMongoDB useCollections (String ... collectionNames ) {
225+ return useCollections (Arrays .asList (collectionNames ));
226+ }
227+
228+ private CleanMongoDB useCollections (Collection <String > collectionNames ) {
229+ return useCollections ("" , collectionNames );
230+ }
231+
232+ /**
233+ * Defines the {@link DBCollection}s and {@link DB} to be used. <br />
234+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
235+ * <ul>
236+ * <li>{@link Struct#COLLECTION}: Forces drop of named collections in given db.</li>
237+ * <li>{@link Struct#INDEX}: Removes index within named collections in given db.</li>
238+ * </ul>
239+ *
240+ * @param collectionNames
241+ * @return
242+ */
243+ public CleanMongoDB useCollections (String db , Collection <String > collectionNames ) {
244+
245+ if (StringUtils .hasText (db )) {
246+ this .dbNames .add (db );
247+ }
248+
249+ if (!CollectionUtils .isEmpty (collectionNames )) {
250+ this .collectionNames .addAll (collectionNames );
251+ }
252+ return this ;
253+ }
254+
255+ Statement apply () {
101256 return apply (null , null );
102257 }
103258
259+ /*
260+ * (non-Javadoc)
261+ * @see org.junit.rules.TestRule#apply(org.junit.runners.model.Statement, org.junit.runner.Description)
262+ */
104263 public Statement apply (Statement base , Description description ) {
105264 return new MongoCleanStatement (base );
106265 }
107266
267+ private void doClean () {
268+
269+ Collection <String > dbNamesToUse = initDbNames ();
270+
271+ for (String dbName : dbNamesToUse ) {
272+
273+ if (isPreserved (dbName ) || dropDbIfRequired (dbName )) {
274+ continue ;
275+ }
276+
277+ DB db = client .getDB (dbName );
278+ dropCollectionsOrIndexIfRequried (db , initCollectionNames (db ));
279+ }
280+ }
281+
282+ private boolean dropDbIfRequired (String dbName ) {
283+
284+ if (!types .contains (Struct .DATABASE )) {
285+ return false ;
286+ }
287+
288+ client .dropDatabase (dbName );
289+ LOGGER .debug ("Dropping DB '{}'. " , dbName );
290+ return true ;
291+ }
292+
293+ private void dropCollectionsOrIndexIfRequried (DB db , Collection <String > collectionsToUse ) {
294+
295+ for (String collectionName : collectionsToUse ) {
296+
297+ if (db .collectionExists (collectionName )) {
298+
299+ DBCollection collection = db .getCollectionFromString (collectionName );
300+ if (collection != null ) {
301+
302+ if (types .contains (Struct .COLLECTION )) {
303+ collection .drop ();
304+ LOGGER .debug ("Dropping collection '{}' for DB '{}'. " , collectionName , db .getName ());
305+ } else if (types .contains (Struct .INDEX )) {
306+ collection .dropIndexes ();
307+ LOGGER .debug ("Dropping indexes in collection '{}' for DB '{}'. " , collectionName , db .getName ());
308+ }
309+ }
310+ }
311+ }
312+ }
313+
314+ private boolean isPreserved (String dbName ) {
315+ return preserveDatabases .contains (dbName .toLowerCase ());
316+ }
317+
318+ private Collection <String > initDbNames () {
319+
320+ Collection <String > dbNamesToUse = dbNames ;
321+ if (dbNamesToUse .isEmpty ()) {
322+ dbNamesToUse = client .getDatabaseNames ();
323+ }
324+ return dbNamesToUse ;
325+ }
326+
327+ private Collection <String > initCollectionNames (DB db ) {
328+
329+ Collection <String > collectionsToUse = collectionNames ;
330+ if (CollectionUtils .isEmpty (collectionsToUse )) {
331+ collectionsToUse = db .getCollectionNames ();
332+ }
333+ return collectionsToUse ;
334+ }
335+
336+ /**
337+ * @author Christoph Strobl
338+ * @since 1.6
339+ */
108340 private class MongoCleanStatement extends Statement {
109341
110342 private final Statement base ;
@@ -126,61 +358,12 @@ public void evaluate() throws Throwable {
126358 isInternal = true ;
127359 }
128360
129- Collection <String > dbNamesToUse = dbNames ;
130- if (dbNamesToUse .isEmpty ()) {
131- dbNamesToUse = client .getDatabaseNames ();
132- }
133-
134- for (String dbName : dbNamesToUse ) {
135-
136- if (preserveDatabases .contains (dbName .toLowerCase ())) {
137- continue ;
138- }
139-
140- if (types .contains (Types .DATABASE )) {
141- client .dropDatabase (dbName );
142- LOGGER .debug ("Dropping DB '{}'. " , dbName );
143- }
144-
145- if (types .contains (Types .COLLECTION )) {
146-
147- DB db = client .getDB (dbName );
148- Collection <String > collectionsToUse = initCollectionNames (db );
149- for (String collectionName : collectionsToUse ) {
150- if (db .collectionExists (collectionName )) {
151- db .getCollectionFromString (collectionName ).drop ();
152- LOGGER .debug ("Dropping collection '{}' for DB '{}'. " , collectionName , dbName );
153- }
154- }
155- }
156-
157- if (types .contains (Types .INDEX )) {
158-
159- DB db = client .getDB (dbName );
160- Collection <String > collectionsToUse = initCollectionNames (db );
161- for (String collectionName : collectionsToUse ) {
162- if (db .collectionExists (collectionName )) {
163- db .getCollectionFromString (collectionName ).dropIndexes ();
164- LOGGER .debug ("Dropping indexes in collection '{}' for DB '{}'. " , collectionName , dbName );
165- }
166- }
167- }
168- }
361+ doClean ();
169362
170363 if (isInternal ) {
171364 client .close ();
172365 client = null ;
173366 }
174367 }
175-
176- private Collection <String > initCollectionNames (DB db ) {
177-
178- Collection <String > collectionsToUse = collectionNames ;
179- if (CollectionUtils .isEmpty (collectionsToUse )) {
180- collectionsToUse = db .getCollectionNames ();
181- }
182- return collectionsToUse ;
183- }
184368 }
185-
186369}
0 commit comments