Java tutorial
/* * * Copyright (c) 2015 University of Massachusetts * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * Initial developer(s): Westy * */ package edu.umass.cs.gnsserver.database; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.BulkWriteOperation; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.DuplicateKeyException; import com.mongodb.MongoClient; import com.mongodb.MongoException; import com.mongodb.util.JSON; import edu.umass.cs.gnscommon.GNSProtocol; import edu.umass.cs.gnscommon.exceptions.server.FailedDBOperationException; import edu.umass.cs.gnscommon.exceptions.server.RecordExistsException; import edu.umass.cs.gnscommon.exceptions.server.RecordNotFoundException; import edu.umass.cs.gnsserver.gnsapp.clientCommandProcessor.commandSupport.AccountAccess; import edu.umass.cs.gnsserver.gnsapp.clientCommandProcessor.commandSupport.MetaDataTypeName; import edu.umass.cs.gnsserver.gnsapp.recordmap.NameRecord; import edu.umass.cs.gnsserver.main.GNSConfig; import edu.umass.cs.gnsserver.utils.JSONUtils; import edu.umass.cs.gnsserver.utils.ValuesMap; import edu.umass.cs.utils.Config; import edu.umass.cs.utils.DelayProfiler; import edu.umass.cs.utils.Util; /** * Provides insert, updateEntireRecord, removeEntireRecord and lookupEntireRecord operations for * guid, key, record triples using JSONObjects as the intermediate representation. * All records are stored in a document called NameRecord. * * @author westy, Abhigyan, arun */ public class MongoRecords implements NoSQLRecords { private static final String DBROOTNAME = "UMASS_GNS_DB_"; /** * The name of the document where name records are stored. */ public static final String DBNAMERECORD = "NameRecord"; private DB db; private String dbName; private MongoClient mongoClient; private MongoCollectionSpecs mongoCollectionSpecs; /** * Creates database tables for nodeID, by connecting to mongoDB on default port. * * @param nodeID nodeID of name server */ public MongoRecords(String nodeID) { this(nodeID, -1); } /** * Creates database tables for nodeID, by connecting to mongoDB on given port. * * @param nodeID nodeID of name server * @param port port at which mongo is running. if port = -1, mongo connects to default port. */ public MongoRecords(String nodeID, int port) { init(nodeID, port); } private void init(String nodeID, int mongoPort) { if (Config.getGlobalBoolean(GNSConfig.GNSC.IN_MEMORY_DB)) { return; } mongoCollectionSpecs = new MongoCollectionSpecs(); mongoCollectionSpecs.addCollectionSpec(DBNAMERECORD, NameRecord.NAME); // add location as another index mongoCollectionSpecs.getCollectionSpec(DBNAMERECORD).addOtherIndex(new BasicDBObject( NameRecord.VALUES_MAP.getName() + "." + GNSProtocol.LOCATION_FIELD_NAME.toString(), "2d")); // The good thing is that indexes are not required for 2dsphere fields, but they will make things faster mongoCollectionSpecs.getCollectionSpec(DBNAMERECORD).addOtherIndex(new BasicDBObject( NameRecord.VALUES_MAP.getName() + "." + GNSProtocol.LOCATION_FIELD_NAME_2D_SPHERE.toString(), "2dsphere")); mongoCollectionSpecs.getCollectionSpec(DBNAMERECORD).addOtherIndex(new BasicDBObject( NameRecord.VALUES_MAP.getName() + "." + GNSProtocol.IPADDRESS_FIELD_NAME.toString(), 1)); boolean fatalException = false; try { // use a unique name in case we have more than one on a machine (need to remove periods, btw) dbName = DBROOTNAME + sanitizeDBName(nodeID); //MongoClient mongoClient; //MongoCredential credential = MongoCredential.createMongoCRCredential("admin", dbName, "changeit".toCharArray()); if (mongoPort > 0) { //mongoClient = new MongoClient(new ServerAddress("localhost", mongoPort), Arrays.asList(credential)); mongoClient = new MongoClient("localhost", mongoPort); } else { mongoClient = new MongoClient("localhost"); } db = mongoClient.getDB(dbName); initializeIndexes(); } catch (UnknownHostException e) { fatalException = true; DatabaseConfig.getLogger().log(Level.SEVERE, "{0} Unable to open Mongo DB: {1}", new Object[] { dbName, e.getMessage() }); } catch (com.mongodb.MongoServerSelectionException msse) { fatalException = true; DatabaseConfig.getLogger().log(Level.SEVERE, "{0} Fatal exception while trying to initialize Mongo DB: {1}", new Object[] { dbName, msse }); } finally { if (fatalException) { Util.suicide( "Mongo DB initialization failed likely because a mongo DB server is not listening at the expected port; exiting."); } } } private void initializeIndexes() { for (MongoCollectionSpec spec : mongoCollectionSpecs.allCollectionSpecs()) { initializeIndex(spec.getName()); } } private void initializeIndex(String collectionName) { MongoCollectionSpec spec = mongoCollectionSpecs.getCollectionSpec(collectionName); db.getCollection(spec.getName()).createIndex(spec.getPrimaryIndex(), new BasicDBObject("unique", true)); for (BasicDBObject index : spec.getOtherIndexes()) { db.getCollection(spec.getName()).createIndex(index); } DatabaseConfig.getLogger().log(Level.FINE, "{0} Indexes initialized", dbName); } @Override public void createIndex(String collectionName, String field, String index) { MongoCollectionSpec spec = mongoCollectionSpecs.getCollectionSpec(collectionName); // Prepend this because of the way we store the records. //DBObject index2d = BasicDBObjectBuilder.start(NameRecord.VALUES_MAP.getName() + "." + field, index).get(); DBObject index2d = BasicDBObjectBuilder.start(NameRecord.VALUES_MAP.getName() + "." + field, isIndexInt(index) ? Integer.parseInt(index) : index).get(); db.getCollection(spec.getName()).createIndex(index2d); } /** * This function checks if an index is an int in * the form of a string. In mongodb, there are some * string indexes, and then are some int indexes like 1 * , -1 etc. So, we need to know which one a user * has supplied. * * @param index * @return */ private boolean isIndexInt(String index) { try { Integer.parseInt(index); return true; } catch (NumberFormatException nfe) { return false; } } @Override public void insert(String collectionName, String guid, JSONObject value) throws FailedDBOperationException, RecordExistsException { db.requestStart(); try { db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); DBObject dbObject; try { dbObject = (DBObject) JSON.parse(value.toString()); } catch (Exception e) { throw new FailedDBOperationException(collectionName, guid, "Unable to parse json" + e.getMessage()); } try { collection.insert(dbObject); } catch (DuplicateKeyException e) { throw new RecordExistsException(collectionName, guid); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} insert failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, dbObject.toString(), "Original mongo exception:" + e.getMessage()); } } finally { db.requestDone(); } } @Override public JSONObject lookupEntireRecord(String collectionName, String guid) throws RecordNotFoundException, FailedDBOperationException { return lookupEntireRecord(collectionName, guid, false); } private JSONObject lookupEntireRecord(String collectionName, String guid, boolean explain) throws RecordNotFoundException, FailedDBOperationException { long startTime = System.currentTimeMillis(); db.requestStart(); try { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, guid); DBCursor cursor = collection.find(query); if (explain) { System.out.println(cursor.explain().toString()); } if (cursor.hasNext()) { DBObject obj = cursor.next(); // arun: optimized for the common case of Map @SuppressWarnings("unchecked") JSONObject json = obj instanceof Map ? DiskMapRecords.recursiveCopyMap((Map<String, ?>) obj) : new JSONObject(obj.toString()); // instrumentation DelayProfiler.updateDelay("lookupEntireRecord", startTime); // older style int lookupTime = (int) (System.currentTimeMillis() - startTime); if (lookupTime > 20) { DatabaseConfig.getLogger().log(Level.FINE, "{0} mongoLookup Long delay {1}", new Object[] { dbName, lookupTime }); } return json; } else { throw new RecordNotFoundException(guid); } } catch (JSONException e) { DatabaseConfig.getLogger().log(Level.WARNING, "{0} Unable to parse JSON: {1}", new Object[] { dbName, e.getMessage() }); return null; } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} lookupEntireRecord failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, guid, "Original mongo exception:" + e.getMessage()); } finally { db.requestDone(); } } @Override public HashMap<ColumnField, Object> lookupSomeFields(String collectionName, String guid, ColumnField nameField, ColumnField valuesMapField, ArrayList<ColumnField> valuesMapKeys) throws RecordNotFoundException, FailedDBOperationException { if (guid == null) { DatabaseConfig.getLogger().log(Level.FINE, "{0} GUID is null: {1}", new Object[] { dbName, guid }); throw new RecordNotFoundException(guid); } db.requestStart(); try { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, guid); BasicDBObject projection = new BasicDBObject().append("_id", 0); if (valuesMapField != null && valuesMapKeys != null) { for (int i = 0; i < valuesMapKeys.size(); i++) { String fieldName = valuesMapField.getName() + "." + valuesMapKeys.get(i).getName(); projection.append(fieldName, 1); } } DBObject dbObject = collection.findOne(query, projection); if (dbObject == null) { throw new RecordNotFoundException(guid); } HashMap<ColumnField, Object> hashMap = new HashMap<>(); hashMap.put(nameField, guid);// put the name in the hashmap!! very important!! if (valuesMapField != null && valuesMapKeys != null) { // first we pull all the user values from the dbObject and put in a bson object // FIXME: Why not convert this to a JSONObject right now? We know that's what it is. BasicDBObject bson = (BasicDBObject) dbObject.get(valuesMapField.getName()); DatabaseConfig.getLogger().log(Level.FINER, "{0} @@@@@@@@ {1}", new Object[] { dbName, bson }); // then we run thru each userkey in the valuesMapKeys and pull the // value put stuffing it into the values map ValuesMap valuesMap = new ValuesMap(); for (int i = 0; i < valuesMapKeys.size(); i++) { String userKey = valuesMapKeys.get(i).getName(); if (containsFieldDotNotation(userKey, bson) == false) { DatabaseConfig.getLogger().log(Level.FINE, "{0} DBObject doesn't contain {1}", new Object[] { dbName, userKey }); continue; } try { switch (valuesMapKeys.get(i).type()) { case USER_JSON: Object value = getWithDotNotation(userKey, bson); DatabaseConfig.getLogger().log(Level.FINE, "{0} Object is {1}", new Object[] { dbName, value.toString() }); valuesMap.put(userKey, value); break; case LIST_STRING: valuesMap.putAsArray(userKey, JSONUtils.JSONArrayToResultValue( new JSONArray(getWithDotNotation(userKey, bson).toString()))); break; default: DatabaseConfig.getLogger().log(Level.SEVERE, "{0} ERROR: Error: User keys field {1} is not a known type: {2}", new Object[] { dbName, userKey, valuesMapKeys.get(i).type() }); break; } } catch (JSONException e) { DatabaseConfig.getLogger().log(Level.SEVERE, "{0} Error parsing json: {1}", new Object[] { dbName, e.getMessage() }); e.printStackTrace(); } } hashMap.put(valuesMapField, valuesMap); } return hashMap; } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} lookupSomeFields failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, guid, "Original mongo exception:" + e.getMessage()); } finally { db.requestDone(); } } private Object getWithDotNotation(String key, BasicDBObject bson) throws JSONException { if (key.contains(".")) { int indexOfDot = key.indexOf("."); String subKey = key.substring(0, indexOfDot); BasicDBObject subBson = (BasicDBObject) bson.get(subKey); if (subBson == null) { throw new JSONException(subKey + " is null"); } try { return getWithDotNotation(key.substring(indexOfDot + 1), subBson); } catch (JSONException e) { throw new JSONException(subKey + "." + e.getMessage()); } } else { Object result = bson.get(key); return result; } } private boolean containsFieldDotNotation(String key, BasicDBObject bson) { try { return getWithDotNotation(key, bson) != null; } catch (JSONException e) { return false; } } @Override public boolean contains(String collectionName, String guid) throws FailedDBOperationException { db.requestStart(); try { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, guid); DBCursor cursor = collection.find(query); return cursor.hasNext(); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} contains failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, guid, "Original mongo exception:" + e.getMessage()); } finally { db.requestDone(); } } @Override public void removeEntireRecord(String collectionName, String guid) throws FailedDBOperationException { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); try { DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, guid); collection.remove(query); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} removeEntireRecord failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, guid, "Original mongo exception:" + e.getMessage()); } } @Override public void updateEntireRecord(String collectionName, String guid, ValuesMap valuesMap) throws FailedDBOperationException { BasicDBObject updates = new BasicDBObject(); try { updates.append(NameRecord.VALUES_MAP.getName(), JSON.parse(valuesMap.toString())); } catch (Exception e) { throw new FailedDBOperationException(collectionName, guid, "Unable to parse json" + e.getMessage()); } doUpdate(collectionName, guid, updates); } /** * * @param collectionName * @param values * @throws FailedDBOperationException * @throws RecordExistsException */ public void bulkUpdate(String collectionName, Map<String, JSONObject> values) throws FailedDBOperationException, RecordExistsException { //String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); DBCollection collection = db.getCollection(collectionName); String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); db.requestEnsureConnection(); BulkWriteOperation unordered = collection.initializeUnorderedBulkOperation(); for (Map.Entry<String, JSONObject> entry : values.entrySet()) { BasicDBObject query = new BasicDBObject(primaryKey, entry.getKey()); JSONObject value = entry.getValue(); if (value != null) { DBObject document; try { document = (DBObject) JSON.parse(value.toString()); } catch (Exception e) { throw new FailedDBOperationException(collectionName, "bulkUpdate", "Unable to parse json" + e.getMessage()); } unordered.find(query).upsert().replaceOne(document); } else { unordered.find(query).removeOne(); } } // Maybe check the result? unordered.execute(); } @Override public void updateIndividualFields(String collectionName, String guid, ColumnField valuesMapField, ArrayList<ColumnField> valuesMapKeys, ArrayList<Object> valuesMapValues) throws FailedDBOperationException { BasicDBObject updates = new BasicDBObject(); if (valuesMapField != null && valuesMapKeys != null) { for (int i = 0; i < valuesMapKeys.size(); i++) { String fieldName = valuesMapField.getName() + "." + valuesMapKeys.get(i).getName(); switch (valuesMapKeys.get(i).type()) { case LIST_STRING: // special case for old format updates.append(fieldName, valuesMapValues.get(i)); break; case USER_JSON: // value is any valid JSON updates.append(fieldName, JSONParse(valuesMapValues.get(i))); break; default: DatabaseConfig.getLogger().log(Level.WARNING, "{0} Ignoring unknown format: {1}", new Object[] { dbName, valuesMapKeys.get(i).type() }); break; } } } doUpdate(collectionName, guid, updates); } private void doUpdate(String collectionName, String guid, BasicDBObject updates) throws FailedDBOperationException { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, guid); if (updates.keySet().size() > 0) { long startTime = System.currentTimeMillis(); try { collection.update(query, new BasicDBObject("$set", updates)); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.SEVERE, "{0} doUpdate failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, updates.toString(), "Original mongo exception:" + e.getMessage()); } DelayProfiler.updateDelay("mongoSetUpdate", startTime); long finishTime = System.currentTimeMillis(); if (finishTime - startTime > 10) { DatabaseConfig.getLogger().log(Level.FINE, "{0} Long latency mongoUpdate {1}", new Object[] { dbName, (finishTime - startTime) }); } } } // not sure why the JSON.parse doesn't handle things this way but it doesn't private Object JSONParse(Object object) { if (object instanceof String || object instanceof Number) { return object; } else { return JSON.parse(object.toString()); } } @Override public void removeMapKeys(String collectionName, String name, ColumnField mapField, ArrayList<ColumnField> mapKeys) throws FailedDBOperationException { String primaryKey = mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey().getName(); DBCollection collection = db.getCollection(collectionName); BasicDBObject query = new BasicDBObject(primaryKey, name); BasicDBObject updates = new BasicDBObject(); if (mapField != null && mapKeys != null) { for (int i = 0; i < mapKeys.size(); i++) { String fieldName = mapField.getName() + "." + mapKeys.get(i).getName(); updates.append(fieldName, 1); } } if (updates.keySet().size() > 0) { try { DatabaseConfig.getLogger().log(Level.FINE, "{0} <============>unset{1}<============>", new Object[] { dbName, updates.toString() }); collection.update(query, new BasicDBObject("$unset", updates)); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} removeMapKeys failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, updates.toString(), "Original mongo exception:" + e.getMessage()); } } } /** * Given a key and a value return all the records that have a *user* key with that value. * User keys are stored in the valuesMap field. * The key should be declared as an index otherwise this baby will be slow. * * @param collectionName * @param key * @param value * // * @param explain * @return a MongoRecordCursor * @throws edu.umass.cs.gnscommon.exceptions.server.FailedDBOperationException */ @Override public MongoRecordCursor selectRecords(String collectionName, ColumnField valuesMapField, String key, Object value) throws FailedDBOperationException { return selectRecords(collectionName, valuesMapField, key, value, false); } private MongoRecordCursor selectRecords(String collectionName, ColumnField valuesMapField, String key, Object value, boolean explain) throws FailedDBOperationException { db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); // note that if the value of the key in the database is a list (which it is) this // query will find all records where the value (a list) *contains* an element whose value is the value // //FROM MONGO DOC: Match an Array Element //Equality matches can specify a single element in the array to match. These specifications match //if the array contains at least one element with the specified value. //In the following example, the query matches all documents where the value of the field tags is //an array that contains 'fruit' as one of its elements: //db.inventory.find( { tags: 'fruit' } ) String fieldName = valuesMapField.getName() + "." + key; BasicDBObject query = new BasicDBObject(fieldName, value); //System.out.println("***GNSProtocol.QUERY.toString()***: " + query.toString()); DBCursor cursor = null; try { cursor = collection.find(query); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} selectRecords failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, fieldName, "Original mongo exception:" + e.getMessage()); } if (explain) { System.out.println(cursor.explain().toString()); } return new MongoRecordCursor(cursor, mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey()); } @Override public MongoRecordCursor selectRecordsWithin(String collectionName, ColumnField valuesMapField, String key, String value) throws FailedDBOperationException { return selectRecordsWithin(collectionName, valuesMapField, key, value, false); } private MongoRecordCursor selectRecordsWithin(String collectionName, ColumnField valuesMapField, String key, String value, boolean explain) throws FailedDBOperationException { db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); BasicDBList box = parseJSONArrayLocationStringIntoDBList(value); String fieldName = valuesMapField.getName() + "." + key; BasicDBObject shapeClause = new BasicDBObject("$box", box); BasicDBObject withinClause = new BasicDBObject("$geoWithin", shapeClause); BasicDBObject query = new BasicDBObject(fieldName, withinClause); DBCursor cursor = null; try { cursor = collection.find(query); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} selectRecordsWithin failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, fieldName, "Original mongo exception:" + e.getMessage()); } if (explain) { System.out.println(cursor.explain().toString()); } return new MongoRecordCursor(cursor, mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey()); } private BasicDBList parseJSONArrayLocationStringIntoDBList(String string) { BasicDBList box1 = new BasicDBList(); BasicDBList box2 = new BasicDBList(); BasicDBList box = new BasicDBList(); try { JSONArray json = new JSONArray(string); box1.add(json.getJSONArray(0).getDouble(0)); box1.add(json.getJSONArray(0).getDouble(1)); box2.add(json.getJSONArray(1).getDouble(0)); box2.add(json.getJSONArray(1).getDouble(1)); box.add(box1); box.add(box2); } catch (JSONException e) { DatabaseConfig.getLogger().log(Level.SEVERE, "{0} Unable to parse JSON: {1}", new Object[] { dbName, e.getMessage() }); } return box; } private final static double METERS_PER_DEGREE = 111.12 * 1000; // at the equator @Override public MongoRecordCursor selectRecordsNear(String collectionName, ColumnField valuesMapField, String key, String value, Double maxDistance) throws FailedDBOperationException { return selectRecordsNear(collectionName, valuesMapField, key, value, maxDistance, false); } private MongoRecordCursor selectRecordsNear(String collectionName, ColumnField valuesMapField, String key, String value, Double maxDistance, boolean explain) throws FailedDBOperationException { db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); double maxDistanceInRadians = maxDistance / METERS_PER_DEGREE; BasicDBList tuple = new BasicDBList(); try { JSONArray json = new JSONArray(value); tuple.add(json.getDouble(0)); tuple.add(json.getDouble(1)); } catch (JSONException e) { DatabaseConfig.getLogger().log(Level.SEVERE, "{0} Unable to parse JSON: {1}", new Object[] { dbName, e.getMessage() }); } String fieldName = valuesMapField.getName() + "." + key; BasicDBObject nearClause = new BasicDBObject("$near", tuple).append("$maxDistance", maxDistanceInRadians); BasicDBObject query = new BasicDBObject(fieldName, nearClause); DBCursor cursor = null; try { cursor = collection.find(query); } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} selectNear failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, fieldName, "Original mongo exception:" + e.getMessage()); } if (explain) { System.out.println(cursor.explain().toString()); } return new MongoRecordCursor(cursor, mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey()); } @Override public MongoRecordCursor selectRecordsQuery(String collectionName, ColumnField valuesMapField, String query, List<String> projection) throws FailedDBOperationException { return selectRecordsQuery(collectionName, valuesMapField, query, projection, false); } private MongoRecordCursor selectRecordsQuery(String collectionName, ColumnField valuesMapField, String query, List<String> projection, boolean explain) throws FailedDBOperationException { db.requestEnsureConnection(); DBCollection collection = db.getCollection(collectionName); DBCursor cursor = null; try { if (projection == null // this handles the special case of the user wanting all fields // in the projection || (!projection.isEmpty() && projection.get(0).equals(GNSProtocol.ENTIRE_RECORD.toString()))) { cursor = collection.find(parseMongoQuery(query, valuesMapField)); } else { cursor = collection.find(parseMongoQuery(query, valuesMapField), generateProjection(projection)); } } catch (MongoException e) { DatabaseConfig.getLogger().log(Level.FINE, "{0} selectRecordsQuery failed: {1}", new Object[] { dbName, e.getMessage() }); throw new FailedDBOperationException(collectionName, query, "Original mongo exception:" + e.getMessage()); } if (explain) { System.out.println(cursor.explain().toString()); } return new MongoRecordCursor(cursor, mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey()); } private DBObject parseMongoQuery(String query, ColumnField valuesMapField) { // convert something like this: ~fred : ($gt: 0) into the queryable // format, namely this: {~nr_valuesMap.fred : ($gt: 0)} String edittedQuery = query; edittedQuery = "{" + edittedQuery + "}"; edittedQuery = edittedQuery.replace("(", "{"); edittedQuery = edittedQuery.replace(")", "}"); edittedQuery = edittedQuery.replace("~", valuesMapField.getName() + "."); // Filter out HRN records String guidFilter = "{" + NameRecord.VALUES_MAP.getName() + "." + AccountAccess.GUID_INFO + ": { $exists: true}}"; edittedQuery = buildAndQuery(guidFilter, edittedQuery); try { DatabaseConfig.getLogger().log(Level.FINE, "{0} Edited query = {1}", new Object[] { dbName, edittedQuery }); DBObject parse = (DBObject) JSON.parse(edittedQuery); DatabaseConfig.getLogger().log(Level.FINE, "{0} Parse = {1}", new Object[] { dbName, parse.toString() }); return parse; } catch (Exception e) { throw new MongoException("Unable to parse query", e); } } /** * @param querys * @return Query string. */ public static String buildAndQuery(String... querys) { StringBuilder result = new StringBuilder(); result.append("{$and: ["); String prefix = ""; for (String query : querys) { result.append(prefix); result.append(query); prefix = ","; } result.append("]}"); return result.toString(); } private DBObject generateProjection(List<String> fields) { // produces { field1: true, field2: true ... } DBObject result = new BasicDBObject(); // Always return the guid result.put(NameRecord.NAME.getName(), "true"); // Put this in so the upstream receiver knows that it is a GUID record result.put(NameRecord.VALUES_MAP.getName() + "." + AccountAccess.GUID_INFO, "true"); // aditya: We also read the ACL fields here. We only need to read the read acls for // select requests. Although, by reading the prefix of READ_WHITELIST we read // the full ACL. result.put(NameRecord.VALUES_MAP.getName() + "." + MetaDataTypeName.READ_WHITELIST.getPrefix(), "true"); // Add all the fields in the projection for (String field : fields) { result.put(NameRecord.VALUES_MAP.getName() + "." + field, "true"); } return result; } @Override public MongoRecordCursor getAllRowsIterator(String collectionName) throws FailedDBOperationException { return new MongoRecordCursor(db, collectionName, mongoCollectionSpecs.getCollectionSpec(collectionName).getPrimaryKey()); } @Override public void printAllEntries(String collectionName) throws FailedDBOperationException { MongoRecordCursor cursor = getAllRowsIterator(collectionName); while (cursor.hasNext()) { System.out.println(cursor.nextJSONObject()); } } @Override public String toString() { return "DB " + dbName; } /** * * * Close mongo client before shutting down name server. * As per mongo doc: * "to dispose of an instance, make sure you call MongoClient.close() to clean up resources." */ public void close() { mongoClient.close(); } /** * @param nodeID */ public static void dropNodeDatabase(String nodeID) { MongoClient mongoClient; try { mongoClient = new MongoClient("localhost"); } catch (UnknownHostException e) { DatabaseConfig.getLogger().log(Level.SEVERE, "Unable to open Mongo DB: {0}", e.getMessage()); return; } String dbName = DBROOTNAME + sanitizeDBName(nodeID); mongoClient.dropDatabase(dbName); List<String> names = mongoClient.getDatabaseNames(); for (String name : names) { if (name.startsWith(dbName)) { mongoClient.dropDatabase(name); } } System.out.println("Dropped DB " + dbName); } private static String sanitizeDBName(String nodeID) { return nodeID.replace('.', '_'); } }