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 }