View Javadoc
1   /*
2    * #%L
3    * MongoClientImpl.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.allanbank.mongodb.client;
21  
22  import java.lang.ref.Reference;
23  import java.lang.ref.ReferenceQueue;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import com.allanbank.mongodb.LambdaCallback;
30  import com.allanbank.mongodb.MongoClient;
31  import com.allanbank.mongodb.MongoClientConfiguration;
32  import com.allanbank.mongodb.MongoCursorControl;
33  import com.allanbank.mongodb.MongoDatabase;
34  import com.allanbank.mongodb.MongoIterator;
35  import com.allanbank.mongodb.StreamCallback;
36  import com.allanbank.mongodb.bson.Document;
37  import com.allanbank.mongodb.bson.DocumentAssignable;
38  import com.allanbank.mongodb.bson.element.StringElement;
39  
40  /**
41   * Implements the bootstrap point for interactions with MongoDB.
42   * 
43   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
44   *         mutated in incompatible ways between any two releases of the driver.
45   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
46   */
47  public class MongoClientImpl implements MongoClient {
48  
49      /** The client to interact with MongoDB. */
50      private final Client myClient;
51  
52      /** The set of databases in use. */
53      private final ConcurrentMap<String, Reference<MongoDatabase>> myDatabases;
54  
55      /** The queue of references to the databases that have been reclaimed. */
56      private final ReferenceQueue<MongoDatabase> myReferenceQueue = new ReferenceQueue<MongoDatabase>();
57  
58      /**
59       * Create a new MongoClient.
60       * 
61       * @param client
62       *            The client interface for interacting with the database.
63       */
64      public MongoClientImpl(final Client client) {
65          myClient = client;
66  
67          myDatabases = new ConcurrentHashMap<String, Reference<MongoDatabase>>();
68      }
69  
70      /**
71       * Create a new MongoClient.
72       * 
73       * @param config
74       *            The configuration for interacting with MongoDB.
75       */
76      public MongoClientImpl(final MongoClientConfiguration config) {
77          this(new ClientImpl(config));
78      }
79  
80      /**
81       * {@inheritDoc}
82       * <p>
83       * Overridden to create a new Mongo instance around a SerialClientImpl.
84       * </p>
85       */
86      @Override
87      public MongoClient asSerializedClient() {
88          if (myClient instanceof SerialClientImpl) {
89              return this;
90          }
91  
92          return new MongoClientImpl(new SerialClientImpl((ClientImpl) myClient));
93      }
94  
95      /**
96       * {@inheritDoc}
97       * <p>
98       * Overridden to close the underlying client.
99       * </p>
100      */
101     @Override
102     public void close() {
103         myClient.close();
104     }
105 
106     /**
107      * Returns the client value.
108      * 
109      * @return The client value.
110      */
111     public Client getClient() {
112         return myClient;
113     }
114 
115     /**
116      * {@inheritDoc}
117      * <p>
118      * Overridden to return the clients configuration.
119      * </p>
120      */
121     @Override
122     public MongoClientConfiguration getConfig() {
123         return myClient.getConfig();
124     }
125 
126     /**
127      * {@inheritDoc}
128      * <p>
129      * Overridden to create a {@link MongoDatabase} instance with the given
130      * name.
131      * </p>
132      * 
133      * @see MongoClient#getDatabase(String)
134      */
135     @Override
136     public MongoDatabase getDatabase(final String name) {
137         MongoDatabase database = null;
138         Reference<MongoDatabase> ref = myDatabases.get(name);
139         if (ref != null) {
140             database = ref.get();
141             if (database == null) {
142                 // Reference was take n from the map. Remove it from the map.
143                 myDatabases.remove(name, ref);
144             }
145         }
146 
147         // Create a new one.
148         if (database == null) {
149             database = new MongoDatabaseImpl(this, myClient, name);
150             ref = new NamedReference<MongoDatabase>(name, database,
151                     myReferenceQueue);
152 
153             final Reference<MongoDatabase> existing = myDatabases.putIfAbsent(
154                     name, ref);
155             if (existing != null) {
156                 final MongoDatabase existingDb = existing.get();
157                 if (existingDb != null) {
158                     database = existingDb;
159                 }
160                 // else ... Extremely unlikely but if the reference came and
161                 // went that quick it is the next guys problem to add one. We
162                 // will return the one we created.
163             }
164         }
165 
166         // Clean out any garbage collected references.
167         Reference<?> polled;
168         while ((polled = myReferenceQueue.poll()) != null) {
169             if (polled instanceof NamedReference) {
170                 myDatabases.remove(((NamedReference<?>) polled).getName(),
171                         polled);
172             }
173         }
174 
175         return database;
176     }
177 
178     /**
179      * {@inheritDoc}
180      * <p>
181      * Overridden to issue a listDatabases command against the 'admin' database.
182      * </p>
183      * 
184      * @see com.allanbank.mongodb.Mongo#listDatabaseNames()
185      */
186     @Override
187     public List<String> listDatabaseNames() {
188 
189         final MongoDatabase db = getDatabase("admin");
190         final Document result = db.runAdminCommand("listDatabases");
191 
192         final List<String> names = new ArrayList<String>();
193         for (final StringElement nameElement : result.find(StringElement.class,
194                 "databases", ".*", "name")) {
195 
196             names.add(nameElement.getValue());
197         }
198 
199         return names;
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     @Deprecated
207     public List<String> listDatabases() {
208         return listDatabaseNames();
209     }
210 
211     /**
212      * Restarts an iterator that was previously saved.
213      * 
214      * @param cursorDocument
215      *            The document containing the state of the cursor.
216      * @return The restarted iterator.
217      * @throws IllegalArgumentException
218      *             If the document does not contain a valid cursor state.
219      */
220     @Override
221     public MongoIterator<Document> restart(
222             final DocumentAssignable cursorDocument)
223             throws IllegalArgumentException {
224         return myClient.restart(cursorDocument);
225     }
226 
227     /**
228      * {@inheritDoc}
229      * <p>
230      * Overridden to call {@link #restart(StreamCallback, DocumentAssignable)}
231      * with an adapter for the {@link LambdaCallback}.
232      * </p>
233      */
234     @Override
235     public MongoCursorControl restart(final LambdaCallback<Document> results,
236             final DocumentAssignable cursorDocument)
237             throws IllegalArgumentException {
238         return restart(new LambdaCallbackAdapter<Document>(results),
239                 cursorDocument);
240     }
241 
242     /**
243      * Restarts a document stream from a cursor that was previously saved.
244      * 
245      * @param results
246      *            Callback that will be notified of the results of the cursor.
247      * @param cursorDocument
248      *            The document containing the state of the cursor.
249      * @throws IllegalArgumentException
250      *             If the document does not contain a valid cursor state.
251      */
252     @Override
253     public MongoCursorControl restart(final StreamCallback<Document> results,
254             final DocumentAssignable cursorDocument)
255             throws IllegalArgumentException {
256         return myClient.restart(results, cursorDocument);
257     }
258 }