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