CommonStorage.cpp
Go to the documentation of this file.
1/*
2* This file is part of ArmarX.
3*
4* ArmarX is free software; you can redistribute it and/or modify
5* it under the terms of the GNU General Public License version 2 as
6* published by the Free Software Foundation.
7*
8* ArmarX is distributed in the hope that it will be useful, but
9* WITHOUT ANY WARRANTY; without even the implied warranty of
10* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11* GNU General Public License for more details.
12*
13* You should have received a copy of the GNU General Public License
14* along with this program. If not, see <http://www.gnu.org/licenses/>.
15*
16* @package MemoryX::CommonStorage
17* @author Alexey Kozlov ( kozlov at kit dot edu)
18* @date 2012
19* @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20* GNU General Public License
21*/
22
23#include "CommonStorage.h"
24
25#include <filesystem>
26#include <fstream>
27#include <memory>
28
29#include <Ice/ObjectAdapter.h>
30#include <IceUtil/UUID.h>
31
33#include <ArmarXCore/interface/core/Log.h>
34
35#include "Collection.h"
36#include "Database.h"
37#include "GridFileWrapper.h"
38#include <mongo/client/dbclient.h>
39#include <mongo/client/dbclientinterface.h>
40
41namespace memoryx
42{
43 const char MONGO_ID_FIELD[] = "_id";
44
45 std::string
47 {
48 return "CommonStorage";
49 }
50
51 void
53 {
54 // http://api.mongodb.com/cxx/current/namespacemongo_1_1client.html#aad4213a224b92333d2395839e4c81498
55 // initialize() MUST be called EXACTLY once after entering 'main' and before using the driver.
56 // Do not call initialize() before entering 'main' (i.e. from a static initializer), as it
57 // relies on all static initialization having been completed.
58 mongo::client::initialize();
59
60 accessGridFSFilesMutex.reset(new std::mutex());
61 if (getProperty<std::string>("MongoHost").isSet())
62 {
63 hostAndPort = getProperty<std::string>("MongoHost").getValue();
64 }
65 else if (!getIceProperties()->getProperty("ArmarX.MongoHost").empty())
66 {
67 hostAndPort = getIceProperties()->getProperty("ArmarX.MongoHost");
68 }
69 else if (getenv("MONGODB_HOST"))
70 {
71 hostAndPort = getenv("MONGODB_HOST");
72 }
73 else
74 {
75 hostAndPort = getProperty<std::string>("MongoHost").getValue();
76 }
77
78 if (hostAndPort.find(':') == std::string::npos)
79 {
80 if (!getIceProperties()->getProperty("ArmarX.MongoPort").empty())
81 {
82 hostAndPort += ":" + getIceProperties()->getProperty("ArmarX.MongoPort");
83 }
84 else if (getenv("MONGODB_PORT"))
85 {
86 hostAndPort += ":" + std::string(getenv("MONGODB_PORT"));
87 }
88 }
89
90 ARMARX_IMPORTANT << "Using MongoDB on " << hostAndPort;
91 // dbName = getProperty<std::string>("MongoDBName").getValue();
92
93 useAuth = getProperty<bool>("MongoAuth").getValue();
94 userName = getProperty<std::string>("MongoUser").getValue();
95
96 connectionCheckerTask =
97 new armarx::PeriodicTask<CommonStorage>(this, &CommonStorage::checkConnection, 1000);
98 }
99
100 void
102 {
103 ARMARX_INFO << "Starting MemoryX::CommonStorage";
104
105 while (!connect() && !this->getObjectScheduler()->isTerminationRequested())
106 {
107 sleep(5);
108 }
109
110 pwdDigest =
111 createPasswordDigest(userName, getProperty<std::string>("MongoPassword").getValue());
112 connectionCheckerTask->start();
113 ARMARX_INFO << "MemoryX::CommonStorage started";
114 }
115
116 void
118 {
119 connectionCheckerTask->stop();
120 {
121 std::unique_lock l(openedDatabasesMutex);
122 openedDatabases.clear();
123 }
124 {
125 std::unique_lock l(openedCollectionsMutex);
126 openedCollections.clear();
127 }
128 {
129 std::unique_lock l(openedFilesMutex);
130 openedFiles.clear();
131 }
132 {
133 std::unique_lock l(openedGridFSMutex);
134 openedGridFS.clear();
135 }
136
137 mongo::client::shutdown();
138 }
139
146
147 bool
148 CommonStorage::connect()
149 {
150 try
151 {
152 openedGridFS.clear();
153 conn.reset(new mongo::DBClientConnection);
154 conn->connect(hostAndPort);
155 }
156 catch (const mongo::DBException& e)
157 {
158 ARMARX_ERROR << "Can't connect to MongoDB: " << e.what();
159 return false;
160 }
161 catch (const std::exception& e)
162 {
163 ARMARX_ERROR << "Can't connect to MongoDB: " << e.what();
164 return false;
165 }
166
167 ARMARX_INFO << "Connected to Mongo: host = " << hostAndPort;
168
169 return true;
170 }
171
172 void
173 CommonStorage::checkConnection()
174 {
175 try
176 {
177 mongo::DBClientConnection conn;
178 conn.connect(hostAndPort);
179 conn.getDatabaseNames();
180 }
181 catch (...)
182 {
183 ARMARX_ERROR << "Connection to MongoDB lost";
184 this->getObjectScheduler()->disconnected(true);
185 }
186 }
187
188 CommonStorage::ConnectionWrapper
189 CommonStorage::getConnection()
190 {
191 std::shared_ptr<mongo::DBClientConnection> conn;
192 {
193 std::lock_guard<std::mutex> guard{serverSettingsMutex};
194 if (pool.empty())
195 {
196 while (true)
197 {
198 try
199 {
200 conn.reset(new mongo::DBClientConnection());
201 conn->connect(hostAndPort);
202 }
203 catch (std::exception& e)
204 {
205 ARMARX_ERROR << "Can't connect to MongoDB: " << e.what();
206 continue;
207 }
208 break;
209 }
210 }
211 else
212 {
213 conn = std::move(pool.front());
214 pool.pop_front();
215 }
216 }
217 return {*this, std::move(conn)};
218 }
219
220 std::string
221 CommonStorage::createPasswordDigest(const std::string& username, const std::string& password)
222 {
223 // No real connection is required here.
224 // In later versions of the API this is a static function
225 std::string digest = getConnection().conn().createPasswordDigest(username, password);
226 return digest;
227 }
228
229 std::string
230 CommonStorage::extractDBNameFromNS(const std::string& ns)
231 {
232 const size_t found = ns.find_first_of('.');
233 return (found != std::string::npos) ? ns.substr(0, found) : "";
234 }
235
236 bool
237 CommonStorage::forceAuthenticate(const std::string& dbName,
238 const std::string& userName,
239 const std::string& password)
240 {
241 std::string errmsg;
242 bool result = false;
243 result = getConnection().conn().auth(dbName, userName, pwdDigest, errmsg, false);
244
245 if (result)
246 {
247 authDBs.insert(dbName);
248 }
249
250 return result;
251 }
252
253 bool
254 CommonStorage::authenticateNS(const std::string& ns)
255 {
256 const std::string dbName = extractDBNameFromNS(ns);
257
258 // check that database part is present in ns string
259 if (dbName.empty())
260 {
261 throw DBNotSpecifiedException("Database name not specified for collection: " + ns +
262 ". Please use <dbName>.<collectionName> format!");
263 }
264
265 return authenticateDB(dbName);
266 }
267
268 bool
269 CommonStorage::authenticateDB(const std::string& dbName)
270 {
271 if (!useAuth)
272 {
273 return true;
274 }
275
276 ARMARX_INFO << "Try to Auth: db = " << dbName << ", user = " << userName
277 << ", pwd = " << pwdDigest << std::endl;
278
279 if (authDBs.count(dbName))
280 {
281 return true;
282 }
283 else
284 {
285 return forceAuthenticate(dbName, userName, pwdDigest);
286 }
287 }
288
289 bool
290 CommonStorage::authDB(const std::string& dbName,
291 const std::string& userName,
292 const std::string& password,
293 const Ice::Current&)
294 {
295 pwdDigest = createPasswordDigest(userName, password);
296 return forceAuthenticate(dbName, userName, this->pwdDigest);
297 }
298
299 std::string
301 {
302 return hostAndPort;
303 }
304
305 NameList
306 CommonStorage::getDBNames(const ::Ice::Current&)
307 {
308 authenticateDB("admin");
309
310 {
311 std::list<std::string> result = getConnection().conn().getDatabaseNames();
312 return NameList(result.begin(), result.end());
313 }
314 }
315
316 NameList
317 CommonStorage::getCollectionNames(const ::std::string& dbName, const ::Ice::Current&)
318 {
319 std::list<std::string> result = getConnection().conn().getCollectionNames(dbName);
320 return NameList(result.begin(), result.end());
321 }
322
323 bool
324 CommonStorage::isConnected(const ::Ice::Current& c)
325 {
326 return true;
327 }
328
329 bool
330 CommonStorage::reconnect(const ::std::string& hostAndPort,
331 const ::std::string& userName,
332 const ::std::string& password,
333 const ::Ice::Current&)
334 {
335 this->hostAndPort = hostAndPort;
336 this->userName = userName;
337 this->pwdDigest = createPasswordDigest(userName, password);
338 this->useAuth = !userName.empty();
339 return connect();
340 }
341
342 DatabaseInterfacePrx
343 CommonStorage::requestDatabase(const ::std::string& dbName, const ::Ice::Current& c)
344 {
345 // authenticate if needed
346 if (authenticateDB(dbName))
347 {
348 DatabasePtr db = new Database(this, dbName);
349 Ice::Identity dbId = db->getIceId();
350 {
351 std::unique_lock l(openedDatabasesMutex);
352 openedDatabases[dbId] = db;
353 }
354 Ice::ObjectPrx node = c.adapter->add(db, dbId);
355 return DatabaseInterfacePrx::uncheckedCast(node);
356 }
357 else
358 {
359 throw MongoAuthenticationException("Mongo authentication failed (user = " + userName +
360 ", database = " + dbName + ")");
361 }
362 }
363
364 void
365 CommonStorage::releaseDatabase(const DatabaseInterfacePrx& db, const ::Ice::Current&)
366 {
367 std::unique_lock l(openedDatabasesMutex);
368 openedDatabases.erase(db->ice_getIdentity());
369 }
370
371 CollectionInterfacePrx
372 CommonStorage::requestCollection(const std::string& collectionNS, const ::Ice::Current& c)
373 {
374 // authenticate if needed
375 if (authenticateNS(collectionNS))
376 {
377 CollectionPtr coll = new Collection(this, collectionNS);
378 Ice::Identity collId = coll->getIceId();
379 {
380 std::unique_lock l(openedCollectionsMutex);
381 openedCollections[collId] = coll;
382 }
383 Ice::ObjectPrx node = c.adapter->add(coll, collId);
384 return CollectionInterfacePrx::uncheckedCast(node);
385 }
386 else
387 {
388 throw MongoAuthenticationException("Mongo authentication failed (user = " + userName +
389 ", collection = " + collectionNS + ")");
390 }
391 }
392
393 void
394 CommonStorage::releaseCollection(const CollectionInterfacePrx& coll, const ::Ice::Current& c)
395 {
396 std::unique_lock l(openedCollectionsMutex);
397 openedCollections.erase(coll->ice_getIdentity());
398 }
399
400 void
401 CommonStorage::dropCollection(const std::string& collectionNS, const ::Ice::Current& c)
402 {
403 // authenticate if needed
404 if (authenticateNS(collectionNS))
405 {
406 getConnection().conn().dropCollection(collectionNS);
407 }
408 else
409 {
410 throw MongoAuthenticationException("Mongo authentication failed (user = " + userName +
411 ", collection = " + collectionNS + ")");
412 }
413 }
414
415 std::string
416 CommonStorage::getDocumentId(const mongo::BSONObj& doc)
417 {
418 return getDocumentField(doc, MONGO_ID_FIELD);
419 }
420
421 std::string
422 CommonStorage::getDocumentField(const mongo::BSONObj& doc, const std::string& fieldName)
423 {
424 if (doc.hasField(fieldName.c_str()))
425 {
426 const mongo::BSONElement field = doc[fieldName.c_str()];
427
428 switch (field.type())
429 {
430 case mongo::jstOID:
431 return field.OID().toString();
432
433 case mongo::String:
434 return field.str();
435
436 default:
437 return field.toString(false);
438 }
439 }
440 else
441 {
442 return "";
443 }
444 }
445
447 CommonStorage::getGridFS(const std::string& dbName)
448 {
449 std::unique_lock l(openedGridFSMutex);
450 std::map<std::string, GridFSPtr>::const_iterator it = openedGridFS.find(dbName);
451
452 if (it != openedGridFS.end())
453 {
454 return it->second;
455 }
456 else
457 {
458 GridFSPtr gridFS(new mongo::GridFS(*conn, dbName));
459 openedGridFS[dbName] = gridFS;
460 return gridFS;
461 }
462 }
463
464 std::string
465 CommonStorage::storeFile(const std::string& dbName,
466 const std::string& fileName,
467 const ::std::string& gridFSName /* = "" */,
468 const Ice::Current& c)
469 {
470 GridFSPtr gridfs = getGridFS(dbName);
471
472 {
473 ARMARX_DEBUG << "Storing file: " << VAROUT(gridFSName) << VAROUT(fileName);
474 if (!std::filesystem::exists(fileName))
475 {
476 throw FileNotFoundException("File could not be found: " + fileName, fileName);
477 }
478 std::unique_lock l(*accessGridFSFilesMutex);
479 mongo::GridFile oldFile =
480 gridfs->findFileByName((gridFSName.empty()) ? fileName : gridFSName);
481 const mongo::BSONObj newFileDoc = gridfs->storeFile(fileName, gridFSName);
482 mongo::GridFile newFile =
483 gridfs->findFileByName((gridFSName.empty()) ? fileName : gridFSName);
484
485 std::string oldId;
486 if (keepOldFileIfEqual(oldFile, newFile, newFileDoc, dbName, oldId))
487 {
488 return oldId;
489 }
490
491 return getDocumentId(newFileDoc);
492 }
493 }
494
495 std::string
496 CommonStorage::storeTextFile(const std::string& dbName,
497 const std::string& bufferToStore,
498 const std::string& gridFSName,
499 const Ice::Current& c)
500 {
501 GridFSPtr gridfs = getGridFS(dbName);
502 {
503 if (gridFSName.empty())
504 {
505 throw armarx::LocalException("gridFSName must not be empty");
506 }
507 std::unique_lock l(*accessGridFSFilesMutex);
508 mongo::GridFile oldFile = gridfs->findFileByName(gridFSName);
509 const mongo::BSONObj newFileDoc =
510 gridfs->storeFile(bufferToStore.c_str(), bufferToStore.size(), gridFSName);
511 mongo::GridFile newFile = gridfs->findFileByName(gridFSName);
512
513 std::string oldId;
514 if (keepOldFileIfEqual(oldFile, newFile, newFileDoc, dbName, oldId))
515 {
516 return oldId;
517 }
518 return getDocumentId(newFileDoc);
519 }
520 }
521
522 std::string
523 CommonStorage::storeBinaryFile(const std::string& dbName,
524 const memoryx::Blob& bufferToStore,
525 const std::string& gridFSName,
526 const Ice::Current& c)
527 {
528 GridFSPtr gridfs = getGridFS(dbName);
529 {
530 if (gridFSName.empty())
531 {
532 throw armarx::LocalException("gridFSName must not be empty");
533 }
534 std::unique_lock l(*accessGridFSFilesMutex);
535 mongo::GridFile oldFile = gridfs->findFileByName(gridFSName);
536
537 const mongo::BSONObj newFileDoc = gridfs->storeFile(
538 reinterpret_cast<const char*>(&bufferToStore[0]), bufferToStore.size(), gridFSName);
539 mongo::GridFile newFile = gridfs->findFileByName(gridFSName);
540
541 std::string oldId;
542 if (keepOldFileIfEqual(oldFile, newFile, newFileDoc, dbName, oldId))
543 {
544 return oldId;
545 }
546
547 return getDocumentId(newFileDoc);
548 }
549 }
550
551 bool
552 CommonStorage::keepOldFileIfEqual(const mongo::GridFile& oldFile,
553 const mongo::GridFile newFile,
554 const mongo::BSONObj& newFileDoc,
555 const std::string dbName,
556 std::string& oldId)
557 {
558 if (oldFile.exists())
559 {
560 std::string docId = (oldFile.getFileField("_id").OID().toString());
561
562 if (!docId.empty() && newFile.getMD5() == oldFile.getMD5())
563 {
564 // keep old file
565 removeFileByQuery(dbName, newFileDoc);
566 oldId = docId;
567 return true;
568 }
569 }
570 return false;
571 }
572
573 mongo::GridFile
574 CommonStorage::getFileByQuery(const std::string& dbName, const mongo::BSONObj& query)
575 {
576 GridFSPtr gfs = getGridFS(dbName);
577 {
578 // this mutex was needed to avoid crashes when multiple clients access GridFS
579 std::unique_lock l(*accessGridFSFilesMutex);
580 auto find = [&]()
581 {
582 mongo::GridFile gf = gfs->findFile(query);
583 if (!gf.exists())
584 {
585 ARMARX_WARNING_S << "Could not query file." << VAROUT(dbName) << ";"
586 << VAROUT(query);
587 }
588 return gf;
589 };
590
591 try
592 {
593 return find();
594 }
595 catch (...)
596 {
597 // dirty hack: try twice
598 return find();
599 }
600 }
601 }
602
603 GridFileInterfacePrx
604 CommonStorage::createFileProxy(mongo::GridFile gridFile, const Ice::Current& c)
605 {
606 if (gridFile.exists())
607 {
608 GridFileWrapperPtr fileWrapper = new GridFileWrapper(gridFile, accessGridFSFilesMutex);
609 Ice::Identity fileIceId = fileWrapper->getIceId();
610 {
611 std::unique_lock l(openedFilesMutex);
612 openedFiles[fileIceId] = fileWrapper;
613 }
614 Ice::ObjectPrx node = c.adapter->add(fileWrapper, fileIceId);
615 return GridFileInterfacePrx::uncheckedCast(node);
616 }
617 else
618 {
619 ARMARX_WARNING << "Grid file does not exit " << gridFile.getFilename();
620 return GridFileInterfacePrx();
621 }
622 }
623
624 GridFileInterfacePrx
625 CommonStorage::getFileProxyById(const std::string& dbName,
626 const std::string& fileId,
627 const Ice::Current& c)
628 {
629 const mongo::BSONObj query = BSON(MONGO_ID_FIELD << mongo::OID(fileId));
630 mongo::GridFile gridFile = getFileByQuery(dbName, query);
631 return createFileProxy(gridFile, c);
632 }
633
634 GridFileInterfacePrx
635 CommonStorage::getFileProxyByName(const std::string& dbName,
636 const std::string& gridFSName,
637 const Ice::Current& c)
638 {
639 const mongo::BSONObj query = BSON("filename" << gridFSName);
640 mongo::GridFile gridFile = getFileByQuery(dbName, query);
641 return createFileProxy(gridFile, c);
642 }
643
644 void
645 CommonStorage::releaseFileProxy(const GridFileInterfacePrx& fileProxy, const Ice::Current& c)
646 {
647 if (fileProxy)
648 {
649 std::unique_lock l(openedFilesMutex);
650 c.adapter->remove(fileProxy->ice_getIdentity());
651 openedFiles.erase(fileProxy->ice_getIdentity());
652 }
653 }
654
655 bool
656 CommonStorage::readTextFile(mongo::GridFile& gridFile, std::string& buffer)
657 {
658 if (gridFile.exists())
659 {
660 std::ostringstream ss;
661 gridFile.write(ss);
662 buffer = ss.str();
663 return true;
664 }
665 else
666 {
667 return false;
668 }
669 }
670
671 bool
672 CommonStorage::readBinaryFile(mongo::GridFile& gridFile, memoryx::Blob& buffer)
673 {
674 if (gridFile.exists())
675 {
676 buffer.reserve(gridFile.getContentLength());
677 std::filebuf sb;
678 sb.pubsetbuf((char*)&buffer[0], buffer.capacity());
679 std::iostream os(&sb);
680 gridFile.write(os);
681 os.flush();
682 return true;
683 }
684 else
685 {
686 return false;
687 }
688 }
689
690 bool
691 CommonStorage::getTextFileById(const std::string& dbName,
692 const std::string& fileId,
693 std::string& buffer,
694 const Ice::Current& c)
695 {
696 const mongo::BSONObj query = BSON(MONGO_ID_FIELD << mongo::OID(fileId));
697 mongo::GridFile gridFile = getFileByQuery(dbName, query);
698 return readTextFile(gridFile, buffer);
699 }
700
701 bool
702 CommonStorage::getBinaryFileById(const std::string& dbName,
703 const std::string& fileId,
704 memoryx::Blob& buffer,
705 const Ice::Current& c)
706 {
707 const mongo::BSONObj query = BSON(MONGO_ID_FIELD << mongo::OID(fileId));
708 mongo::GridFile gridFile = getFileByQuery(dbName, query);
709 return readBinaryFile(gridFile, buffer);
710 }
711
712 bool
713 CommonStorage::getTextFileByName(const std::string& dbName,
714 const std::string& gridFSName,
715 std::string& buffer,
716 const Ice::Current& c)
717 {
718 const mongo::BSONObj query = BSON("filename" << gridFSName);
719 mongo::GridFile gridFile = getFileByQuery(dbName, query);
720 return readTextFile(gridFile, buffer);
721 }
722
723 bool
724 CommonStorage::getBinaryFileByName(const std::string& dbName,
725 const std::string& gridFSName,
726 memoryx::Blob& buffer,
727 const Ice::Current& c)
728 {
729 const mongo::BSONObj query = BSON("filename" << gridFSName);
730 mongo::GridFile gridFile = getFileByQuery(dbName, query);
731 return readBinaryFile(gridFile, buffer);
732 }
733
734 void
735 CommonStorage::removeFileByQuery(const std::string& dbName, const mongo::BSONObj& fileQuery)
736 {
737 std::string GridFsFilesNamespace = dbName + ".fs.files";
738 std::string GridFsChunksNamespace = dbName + ".fs.chunks";
739
740 try
741 {
742 auto conn = getConnection();
743 std::unique_ptr<mongo::DBClientCursor> files =
744 conn.conn().query(GridFsFilesNamespace, fileQuery);
745
746 while (files->more())
747 {
748 mongo::BSONObj file = files->next();
749 mongo::BSONElement id = file["_id"];
750 //ARMARX_LOG << "Removing {(files)_id: " << id << "} from database [" << dbName << "]";
751 conn.conn().remove(GridFsFilesNamespace, BSON("_id" << id));
752 conn.conn().remove(GridFsChunksNamespace, BSON("files_id" << id));
753 }
754 }
755 catch (mongo::DBException& e)
756 {
757 ARMARX_ERROR << "Error removing file by query " << fileQuery << ": " << e.what();
758 }
759 }
760
761 NameList
762 CommonStorage::getFileNameList(const std::string& dbName, const Ice::Current& c)
763 {
764 NameList result;
765 auto gridfs = getGridFS(dbName);
766 std::unique_ptr<mongo::DBClientCursor> list = gridfs->list();
767
768 while (list->more())
769 {
770 mongo::BSONObj query = list->nextSafe();
771 auto file = getFileByQuery(dbName, query);
772 // ARMARX_INFO << file.getFilename() << " - " << file.getContentType();
773 result.push_back(file.getFilename());
774 }
775
776 return result;
777 }
778
779 NameList
780 CommonStorage::getFileIdList(const std::string& dbName, const Ice::Current& c)
781 {
782 NameList result;
783 auto gridfs = getGridFS(dbName);
784 {
785 std::unique_lock l(*accessGridFSFilesMutex);
786
787 std::unique_ptr<mongo::DBClientCursor> list = gridfs->list();
788
789 while (list->more())
790 {
791 mongo::BSONObj query = list->nextSafe();
792 result.push_back(getDocumentId(query));
793 }
794 }
795 return result;
796 }
797
798 bool
799 CommonStorage::removeFileById(const std::string& dbName,
800 const std::string& fileId,
801 const Ice::Current&)
802 {
803 const mongo::BSONObj fileQuery = BSON(MONGO_ID_FIELD << mongo::OID(fileId));
804 auto gridfs = getGridFS(dbName);
805 {
806 std::unique_lock l(*accessGridFSFilesMutex);
807 mongo::GridFile gridFile = gridfs->findFile(fileQuery);
808
809 if (!gridFile.exists())
810 {
811 return false;
812 }
813 }
814
815 removeFileByQuery(dbName, fileQuery);
816 return true;
817 }
818
819 bool
820 CommonStorage::removeFileByName(const std::string& dbName,
821 const std::string& gridFSName,
822 const Ice::Current& c)
823 {
824 auto gridfs = getGridFS(dbName);
825 {
826 std::unique_lock l(*accessGridFSFilesMutex);
827 gridfs->removeFile(gridFSName);
828 }
829 return true;
830 }
831
832 Ice::Int
833 CommonStorage::count(const std::string& ns)
834 {
835 Ice::Int count = 0;
836
837 try
838 {
839 count = getConnection().conn().count(ns);
840 }
841 catch (mongo::DBException& e)
842 {
843 ARMARX_ERROR << "Error on db.count(" << ns << "): " << e.what();
844 }
845
846 return count;
847 }
848
849 DBStorableData
850 CommonStorage::findByMongoId(const std::string& ns, const std::string& id)
851 {
852 // try to init mongo id from provided string
853 mongo::OID oid;
854
855 try
856 {
857 oid.init(id);
858 }
859 catch (mongo::AssertionException& e)
860 {
861 ARMARX_ERROR << "findByMongoId failed for id " << id << ": " << e.what()
862 << "\nNS: " << ns;
863 throw InvalidMongoIdException(e.what(), id);
864 }
865
866 mongo::Query query = mongo::Query(BSON(MONGO_ID_FIELD << oid));
867 DBStorableDataList result = findByMongoQuery(ns, query, true);
868
869 return result.size() > 0 ? result[0] : DBStorableData();
870 }
871
872 DBStorableDataList
873 CommonStorage::findByFieldValue(const std::string& ns,
874 const std::string& fieldName,
875 const ::std::string& fieldValue)
876 {
877 const mongo::Query query = mongo::Query(BSON(fieldName << fieldValue));
878 return findByMongoQuery(ns, query, false);
879 }
880
881 DBStorableDataList
883 const std::string& fieldName,
884 const NameList& fieldValueList)
885 {
886 mongo::BSONArrayBuilder b;
887
888 for (const auto& it : fieldValueList)
889 {
890 b.append(it);
891 }
892
893 const mongo::Query query(BSON(fieldName << mongo::Query(BSON("$in" << b.arr()))));
894 return findByMongoQuery(ns, query, false);
895 }
896
897 DBStorableData
899 const std::string& fieldName,
900 const ::std::string& fieldValue)
901 {
902 const mongo::Query query = mongo::Query(BSON(fieldName << fieldValue));
903 const DBStorableDataList result = findByMongoQuery(ns, query, false);
904 return result.size() > 0 ? result[0] : DBStorableData();
905 }
906
907 DBStorableDataList
908 CommonStorage::findByQuery(const std::string& ns,
909 const std::string& query,
910 const std::string& where)
911 {
912 mongo::Query q(query);
913
914 if (!where.empty())
915 {
916 q.where(where);
917 }
918
919 return findByMongoQuery(ns, q, false);
920 }
921
922 DBStorableData
923 CommonStorage::findOneByQuery(const std::string& ns, const std::string& query)
924 {
925 const DBStorableDataList result = findByMongoQuery(ns, mongo::Query(query), false);
926 return result.size() > 0 ? result[0] : DBStorableData();
927 }
928
929 DBStorableDataList
930 CommonStorage::findAll(const std::string& ns)
931 {
932 const mongo::Query query;
933 return findByMongoQuery(ns, query, false);
934 }
935
936 DBStorableData
937 CommonStorage::findAllUniqueByFieldName(const std::string& ns, const ::std::string& fieldName)
938 {
939 auto conn = getConnection();
940 mongo::BSONObj fetch;
941 std::size_t dotPosition = ns.find_first_of(".");
942 std::string databaseName = ns.substr(0, dotPosition);
943 std::string collectionName = ns.substr(dotPosition + 1, ns.size());
944 conn.conn().runCommand(
945 databaseName, BSON("distinct" << collectionName << "key" << fieldName), fetch);
946 mongo::BSONObj fetchedValues = fetch.getObjectField("values");
947 DBStorableData result;
948 result.JSON = fetchedValues.jsonString();
949 return result;
950 }
951
952 EntityIdList
953 CommonStorage::findAllIds(const std::string& ns)
954 {
955 const mongo::Query query;
956 return (EntityIdList)findFieldByMongoQuery(ns, query, MONGO_ID_FIELD);
957 }
958
959 NameList
960 CommonStorage::findAllFieldValues(const std::string& ns, const std::string& fieldName)
961 {
962 const mongo::Query query;
963 return findFieldByMongoQuery(ns, query, fieldName);
964 }
965
966 NameList
967 CommonStorage::findFieldByMongoQuery(const std::string& ns,
968 const mongo::Query& query,
969 const std::string& fieldName)
970 {
971 NameList result;
972 try
973 {
974 auto conn = getConnection();
975 boost::scoped_ptr<mongo::DBClientCursor> cursor(conn.conn().query(ns, query));
976
977 while (cursor->more())
978 {
979 result.push_back(getDocumentField(cursor->next(), fieldName));
980 }
981 }
982 catch (mongo::DBException& e)
983 {
984 ARMARX_ERROR << "Error fetching field values by query: " << e.what();
985 }
986
987 return result;
988 }
989
990 DBStorableDataList
991 CommonStorage::findByMongoQuery(const std::string& ns,
992 const mongo::Query& query,
993 bool justOne /* = false */)
994 {
995 DBStorableDataList result;
996 try
997 {
998 auto conn = getConnection();
999 boost::scoped_ptr<mongo::DBClientCursor> cursor(
1000 conn.conn().query(ns, query, justOne ? 1 : 0));
1001
1002 while (cursor->more())
1003 {
1004 DBStorableData obj;
1005 obj.JSON = cursor->nextSafe().jsonString();
1006 result.push_back(obj);
1007 }
1008 }
1009 catch (mongo::DBException& e)
1010 {
1011 ARMARX_ERROR << "Error fetching objects by query: " << e.what();
1012 }
1013
1014 return result;
1015 }
1016
1017 std::string
1018 CommonStorage::insert(const std::string& ns,
1019 const DBStorableData& obj,
1020 bool upsert /* = false */)
1021 {
1022 std::string result = "";
1023
1024 try
1025 {
1026 mongo::BSONObj bsonObj = mongo::fromjson(obj.JSON);
1027
1028 // check if _id field already present; if not, it must be generated here
1029 if (bsonObj.hasField(MONGO_ID_FIELD))
1030 {
1031 result = getDocumentId(bsonObj);
1032 }
1033 else
1034 {
1035 // we need this trick to return generated mongoID to caller
1036 mongo::BSONObjBuilder builder;
1037 mongo::OID newID = mongo::OID::gen();
1038 builder.append(MONGO_ID_FIELD, newID);
1039 builder.appendElements(bsonObj);
1040 bsonObj = builder.obj();
1041 result = newID.toString();
1042 }
1043
1044 if (upsert)
1045 {
1046 mongo::Query query = mongo::Query(BSON(MONGO_ID_FIELD << bsonObj[MONGO_ID_FIELD]));
1047 getConnection().conn().update(ns, query, bsonObj, true);
1048 }
1049 else
1050 {
1051 getConnection().conn().insert(ns, bsonObj);
1052 }
1053 }
1054 catch (mongo::DBException& e)
1055 {
1056 ARMARX_ERROR << "Error inserting object: " << e.what();
1057 }
1058
1059 return result;
1060 }
1061
1062 std::vector<std::string>
1063 CommonStorage::insertList(const std::string& ns, const DBStorableDataList& objectList)
1064 {
1065 std::vector<std::string> result(objectList.size(), "");
1066
1067 try
1068 {
1069 std::vector<mongo::BSONObj> bsonObjects;
1070 bsonObjects.reserve(objectList.size());
1071
1072 for (size_t i = 0; i < objectList.size(); i++)
1073 {
1074 bsonObjects.push_back(mongo::fromjson(objectList[i].JSON));
1075 result[i] = getDocumentId(bsonObjects[i]);
1076
1077 // check if _id field already present; if not, it must be generated here
1078 if (!bsonObjects[i].hasField(MONGO_ID_FIELD))
1079 {
1080 // we need this trick to return the generated mongoID to caller
1081 mongo::OID newID = mongo::OID::gen();
1082 result[i] = newID.toString();
1083
1084 mongo::BSONObjBuilder builder;
1085 builder.append(MONGO_ID_FIELD, newID);
1086 builder.appendElements(bsonObjects[i]);
1087 bsonObjects[i] = builder.obj();
1088 }
1089 }
1090
1091 getConnection().conn().insert(ns, bsonObjects);
1092 }
1093 catch (mongo::DBException& e)
1094 {
1095 ARMARX_ERROR << "Error inserting object: " << e.what();
1096 }
1097
1098 return result;
1099 }
1100
1101 bool
1102 CommonStorage::update(const std::string& ns,
1103 const DBStorableData& obj,
1104 const std::string& keyField,
1105 bool upsert /*= false*/)
1106 {
1107 bool result = false;
1108
1109 try
1110 {
1111 const mongo::BSONObj mongoObj = mongo::fromjson(obj.JSON);
1112
1113 if (!mongoObj.hasField(keyField.c_str()))
1114 {
1115 throw FieldNotFoundException("field not found in supplied JSON object", keyField);
1116 }
1117
1118 mongo::Query query(BSON(keyField << mongoObj[keyField]));
1119 {
1120 getConnection().conn().update(ns, query, mongoObj, upsert);
1121 }
1122 result = true;
1123 }
1124 catch (const mongo::DBException& e)
1125 {
1126 ARMARX_ERROR << "Error updating object: " << e.what();
1127 }
1128
1129 return result;
1130 }
1131
1132 bool
1133 CommonStorage::updateByQuery(const std::string& ns,
1134 const std::string& query,
1135 const mongo::BSONObj& obj)
1136 {
1137 bool result = false;
1138
1139 try
1140 {
1141 conn->update(ns, query, obj);
1142 result = true;
1143 }
1144 catch (mongo::DBException& e)
1145 {
1146 ARMARX_ERROR << "Error updating object: " << e.what();
1147 }
1148
1149 return result;
1150 }
1151
1152 bool
1153 CommonStorage::removeByMongoId(const std::string& ns, const std::string& id)
1154 {
1155 // try to init mongo id from provided string
1156 mongo::OID oid;
1157
1158 try
1159 {
1160 oid.init(id);
1161 }
1162 catch (mongo::AssertionException& e)
1163 {
1164 throw InvalidMongoIdException(e.what(), id);
1165 }
1166
1167 mongo::Query query(BSON(MONGO_ID_FIELD << oid));
1168 return removeByMongoQuery(ns, query);
1169 }
1170
1171 bool
1173 const std::string& fieldName,
1174 const std::string& fieldValue)
1175 {
1176 const mongo::Query query(BSON(fieldName << fieldValue));
1177 return removeByMongoQuery(ns, query);
1178 }
1179
1180 bool
1181 CommonStorage::removeByQuery(const std::string& ns, const std::string& query)
1182 {
1183 return removeByMongoQuery(ns, mongo::Query(query));
1184 }
1185
1186 bool
1187 CommonStorage::clearCollection(const std::string& ns)
1188 {
1189 return removeByMongoQuery(ns, mongo::Query());
1190 }
1191
1192 bool
1193 CommonStorage::removeByMongoQuery(const std::string& ns, const mongo::Query& query)
1194 {
1195 int result = false;
1196
1197 try
1198 {
1199 getConnection().conn().remove(ns, query);
1200 result = true;
1201 }
1202 catch (mongo::DBException& e)
1203 {
1204 ARMARX_ERROR << "Error deleting objects by query: " << e.what();
1205 }
1206
1207 return result;
1208 }
1209
1210 bool
1211 CommonStorage::ensureIndex(const std::string& ns, const std::string& fieldName, bool unique)
1212 {
1213 int result = false;
1214
1215 try
1216 {
1217 const mongo::BSONObj keys = BSON(fieldName << 1);
1218 // the ubuntu 14.4 version of the lib does not has a define to querry the version
1219 // due to breaking changes in the interface, we have to call different functions here.
1220 // so we use the macro MONGOCLIENT_VERSION to decide which interface version we use
1221#ifdef MONGOCLIENT_VERSION
1222 getConnection().conn().createIndex(ns, keys);
1223#else
1224 getConnection().conn().ensureIndex(ns, keys, unique);
1225#endif
1226
1227 result = true;
1228 }
1229 catch (mongo::DBException& e)
1230 {
1231 ARMARX_ERROR << "Error ensuring index: " << e.what();
1232 }
1233
1234 return result;
1235 }
1236
1237 CommonStorage::ConnectionWrapper::ConnectionWrapper(
1238 CommonStorage& storage,
1239 std::shared_ptr<mongo::DBClientConnection> connPtr) :
1240 connPtr{std::move(connPtr)}, hostAndPort{storage.hostAndPort}, storage{&storage}
1241 {
1242 }
1243
1244 CommonStorage::ConnectionWrapper::~ConnectionWrapper()
1245 {
1246 std::lock_guard<std::mutex> guard{storage->serverSettingsMutex};
1247 if (hostAndPort == storage->hostAndPort)
1248 {
1249 storage->pool.emplace_back(std::move(connPtr));
1250 }
1251 }
1252
1253 mongo::DBClientConnection&
1254 CommonStorage::ConnectionWrapper::conn()
1255 {
1256 return *connPtr;
1257 }
1258} // namespace memoryx
#define VAROUT(x)
constexpr T c
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
ArmarXObjectSchedulerPtr getObjectScheduler() const
The periodic task executes one thread method repeatedly using the time period specified in the constr...
Ice::PropertiesPtr getIceProperties() const
Returns the set of Ice properties.
The CommonStorage class provides an interface to MongoDB.
void onInitComponent() override
Pure virtual hook for the subclass.
bool isConnected(const ::Ice::Current &c=Ice::emptyCurrent) override
bool removeByMongoId(const std::string &ns, const std::string &id)
bool getBinaryFileById(const ::std::string &dbName, const ::std::string &fileId, memoryx::Blob &buffer, const ::Ice::Current &c=Ice::emptyCurrent) override
bool removeFileById(const ::std::string &dbName, const ::std::string &fileId, const ::Ice::Current &c=Ice::emptyCurrent) override
bool removeByQuery(const std::string &ns, const std::string &query)
void releaseCollection(const CollectionInterfacePrx &coll, const ::Ice::Current &c=Ice::emptyCurrent) override
GridFileInterfacePrx getFileProxyById(const ::std::string &dbName, const ::std::string &fileId, const ::Ice::Current &c=Ice::emptyCurrent) override
Ice::Int count(const std::string &ns)
bool clearCollection(const std::string &ns)
NameList getDBNames(const ::Ice::Current &=Ice::emptyCurrent) override
std::string storeBinaryFile(const ::std::string &dbName, const memoryx::Blob &bufferToStore, const ::std::string &gridFSName="", const ::Ice::Current &c=Ice::emptyCurrent) override
bool getTextFileById(const ::std::string &dbName, const ::std::string &fileId, ::std::string &buffer, const ::Ice::Current &c=Ice::emptyCurrent) override
DBStorableData findByMongoId(const std::string &ns, const std::string &id)
GridFileInterfacePrx getFileProxyByName(const ::std::string &dbName, const ::std::string &gridFSName, const ::Ice::Current &c=Ice::emptyCurrent) override
NameList getFileIdList(const std::string &dbName, const Ice::Current &c=Ice::emptyCurrent) override
bool removeFileByName(const ::std::string &dbName, const ::std::string &gridFSName, const ::Ice::Current &c=Ice::emptyCurrent) override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
std::string insert(const std::string &ns, const DBStorableData &obj, bool upsert=false)
DBStorableDataList findByQuery(const std::string &ns, const std::string &query, const std::string &where="")
void releaseFileProxy(const GridFileInterfacePrx &fileProxy, const ::Ice::Current &c=Ice::emptyCurrent) override
bool removeByFieldValue(const std::string &ns, const std::string &fieldName, const std::string &fieldValue)
DBStorableDataList findAll(const std::string &ns)
NameList findAllFieldValues(const std::string &ns, const std::string &fieldName)
bool update(const std::string &ns, const DBStorableData &obj, const std::string &keyField, bool upsert=false)
std::string storeTextFile(const ::std::string &dbName, const ::std::string &bufferToStore, const ::std::string &gridFSName="", const ::Ice::Current &c=Ice::emptyCurrent) override
std::vector< std::string > insertList(const std::string &ns, const DBStorableDataList &objectList)
DBStorableData findOneByQuery(const std::string &ns, const std::string &query)
DBStorableData findOneByFieldValue(const std::string &ns, const std::string &fieldName, const ::std::string &fieldValue)
DBStorableDataList findByFieldValue(const std::string &ns, const std::string &fieldName, const ::std::string &fieldValue)
void onConnectComponent() override
Pure virtual hook for the subclass.
CollectionInterfacePrx requestCollection(const std::string &collectionNS, const ::Ice::Current &c=Ice::emptyCurrent) override
DBStorableData findAllUniqueByFieldName(const std::string &ns, const ::std::string &fieldName)
bool ensureIndex(const std::string &ns, const std::string &fieldName, bool unique)
DatabaseInterfacePrx requestDatabase(const ::std::string &dbName, const ::Ice::Current &=Ice::emptyCurrent) override
NameList getCollectionNames(const ::std::string &dbName, const ::Ice::Current &=Ice::emptyCurrent) override
bool updateByQuery(const std::string &ns, const std::string &query, const mongo::BSONObj &obj)
EntityIdList findAllIds(const std::string &ns)
bool reconnect(const ::std::string &hostAndPort, const ::std::string &userName, const ::std::string &password, const ::Ice::Current &=Ice::emptyCurrent) override
void releaseDatabase(const DatabaseInterfacePrx &db, const ::Ice::Current &=Ice::emptyCurrent) override
bool authDB(const ::std::string &dbName, const ::std::string &userName, const ::std::string &password, const ::Ice::Current &=Ice::emptyCurrent) override
std::string storeFile(const ::std::string &dbName, const ::std::string &fileName, const ::std::string &gridFSName="", const ::Ice::Current &c=Ice::emptyCurrent) override
void onExitComponent() override
Hook for subclass.
void dropCollection(const std::string &collectionNS, const ::Ice::Current &c=Ice::emptyCurrent) override
NameList getFileNameList(const std::string &dbName, const Ice::Current &c=Ice::emptyCurrent) override
DBStorableDataList findByFieldValueList(const std::string &ns, const std::string &fieldName, const NameList &fieldValueList)
std::string getMongoHostAndPort(const ::Ice::Current &c=Ice::emptyCurrent) override
std::string getDefaultName() const override
Retrieve default name of component.
bool getBinaryFileByName(const ::std::string &dbName, const ::std::string &gridFSName, memoryx::Blob &buffer, const ::Ice::Current &c=Ice::emptyCurrent) override
void removeFileByQuery(const std::string &dbName, const mongo::BSONObj &fileQuery)
bool getTextFileByName(const ::std::string &dbName, const ::std::string &gridFSName, ::std::string &buffer, const ::Ice::Current &c=Ice::emptyCurrent) override
The Database class provides an interface to a database.
Definition Database.h:36
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_WARNING_S
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:213
#define q
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
constexpr std::size_t find(string_view str, char_type c) noexcept
VirtualRobot headers.
IceUtil::Handle< Database > DatabasePtr
Definition Database.h:63
IceInternal::Handle< GridFileWrapper > GridFileWrapperPtr
std::shared_ptr< mongo::GridFS > GridFSPtr
const char MONGO_ID_FIELD[]
IceUtil::Handle< Collection > CollectionPtr
Definition Collection.h:104