comparison OrthancServer/OrthancInitialization.cpp @ 2940:4767d36679ed

refactoring access to Orthanc configuration
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 29 Nov 2018 11:47:00 +0100
parents 4a38d7d4f0e0
children bbfd95a0c429
comparison
equal deleted inserted replaced
2939:577786f59252 2940:4767d36679ed
37 // "Please include winsock2.h before windows.h" 37 // "Please include winsock2.h before windows.h"
38 # include <winsock2.h> 38 # include <winsock2.h>
39 #endif 39 #endif
40 40
41 #include "OrthancInitialization.h" 41 #include "OrthancInitialization.h"
42 #include "ServerContext.h" 42
43 43 #include "../Core/DicomParsing/FromDcmtkBridge.h"
44 #include "../Core/HttpClient.h" 44 #include "../Core/FileStorage/FilesystemStorage.h"
45 #include "../Core/Logging.h" 45 #include "../Core/Logging.h"
46 #include "../Core/OrthancException.h" 46 #include "../Core/OrthancException.h"
47 #include "../Core/Toolbox.h" 47
48 #include "../Core/FileStorage/FilesystemStorage.h"
49
50 #include "ServerEnumerations.h"
51 #include "DatabaseWrapper.h" 48 #include "DatabaseWrapper.h"
52 #include "../Core/DicomParsing/FromDcmtkBridge.h" 49 #include "OrthancConfiguration.h"
53
54 #include <boost/lexical_cast.hpp>
55 #include <boost/filesystem.hpp>
56 #include <curl/curl.h>
57 #include <boost/thread/recursive_mutex.hpp>
58 50
59 #include <dcmtk/dcmnet/dul.h> // For dcmDisableGethostbyaddr() 51 #include <dcmtk/dcmnet/dul.h> // For dcmDisableGethostbyaddr()
60 52
61 53
62 54
63 namespace Orthanc 55 namespace Orthanc
64 { 56 {
65 static boost::recursive_mutex globalMutex_; 57 static void RegisterUserMetadata(const Json::Value& config)
66 static Json::Value configuration_; 58 {
67 static boost::filesystem::path defaultDirectory_; 59 if (config.isMember("UserMetadata"))
68 static std::string configurationAbsolutePath_; 60 {
69 static FontRegistry fontRegistry_; 61 const Json::Value& parameter = config["UserMetadata"];
70 static const char* configurationFileArg_ = NULL;
71
72
73 static std::string GetGlobalStringParameterInternal(const std::string& parameter,
74 const std::string& defaultValue)
75 {
76 if (configuration_.isMember(parameter))
77 {
78 if (configuration_[parameter].type() != Json::stringValue)
79 {
80 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a string";
81 throw OrthancException(ErrorCode_BadParameterType);
82 }
83 else
84 {
85 return configuration_[parameter].asString();
86 }
87 }
88 else
89 {
90 return defaultValue;
91 }
92 }
93
94
95 static bool GetGlobalBoolParameterInternal(const std::string& parameter,
96 bool defaultValue)
97 {
98 if (configuration_.isMember(parameter))
99 {
100 if (configuration_[parameter].type() != Json::booleanValue)
101 {
102 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a Boolean (true or false)";
103 throw OrthancException(ErrorCode_BadParameterType);
104 }
105 else
106 {
107 return configuration_[parameter].asBool();
108 }
109 }
110 else
111 {
112 return defaultValue;
113 }
114 }
115
116
117
118 static void AddFileToConfiguration(Json::Value& target,
119 const boost::filesystem::path& path)
120 {
121 std::map<std::string, std::string> env;
122 SystemToolbox::GetEnvironmentVariables(env);
123
124 LOG(WARNING) << "Reading the configuration from: " << path;
125
126 Json::Value config;
127
128 {
129 std::string content;
130 SystemToolbox::ReadFile(content, path.string());
131
132 content = Toolbox::SubstituteVariables(content, env);
133
134 Json::Value tmp;
135 Json::Reader reader;
136 if (!reader.parse(content, tmp) ||
137 tmp.type() != Json::objectValue)
138 {
139 LOG(ERROR) << "The configuration file does not follow the JSON syntax: " << path;
140 throw OrthancException(ErrorCode_BadJson);
141 }
142
143 Toolbox::CopyJsonWithoutComments(config, tmp);
144 }
145
146 if (target.size() == 0)
147 {
148 target = config;
149 }
150 else
151 {
152 // Merge the newly-added file with the previous content of "target"
153 Json::Value::Members members = config.getMemberNames();
154 for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
155 {
156 if (target.isMember(members[i]))
157 {
158 LOG(ERROR) << "The configuration section \"" << members[i] << "\" is defined in 2 different configuration files";
159 throw OrthancException(ErrorCode_BadFileFormat);
160 }
161 else
162 {
163 target[members[i]] = config[members[i]];
164 }
165 }
166 }
167 }
168
169
170 static void ScanFolderForConfiguration(Json::Value& target,
171 const char* folder)
172 {
173 using namespace boost::filesystem;
174
175 LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files";
176
177 directory_iterator end_it; // default construction yields past-the-end
178 for (directory_iterator it(folder);
179 it != end_it;
180 ++it)
181 {
182 if (!is_directory(it->status()))
183 {
184 std::string extension = boost::filesystem::extension(it->path());
185 Toolbox::ToLowerCase(extension);
186
187 if (extension == ".json")
188 {
189 AddFileToConfiguration(target, it->path().string());
190 }
191 }
192 }
193 }
194
195
196 static void ReadConfiguration(Json::Value& target,
197 const char* configurationFile)
198 {
199 target = Json::objectValue;
200
201 if (configurationFile)
202 {
203 if (!boost::filesystem::exists(configurationFile))
204 {
205 LOG(ERROR) << "Inexistent path to configuration: " << configurationFile;
206 throw OrthancException(ErrorCode_InexistentFile);
207 }
208
209 if (boost::filesystem::is_directory(configurationFile))
210 {
211 ScanFolderForConfiguration(target, configurationFile);
212 }
213 else
214 {
215 AddFileToConfiguration(target, configurationFile);
216 }
217 }
218 else
219 {
220 #if ORTHANC_STANDALONE == 1
221 // No default path for the standalone configuration
222 LOG(WARNING) << "Using the default Orthanc configuration";
223 return;
224
225 #else
226 // In a non-standalone build, we use the
227 // "Resources/Configuration.json" from the Orthanc source code
228
229 boost::filesystem::path p = ORTHANC_PATH;
230 p /= "Resources";
231 p /= "Configuration.json";
232
233 AddFileToConfiguration(target, p);
234 #endif
235 }
236 }
237
238
239
240 static void ReadGlobalConfiguration(const char* configurationFile)
241 {
242 // Read the content of the configuration
243 configurationFileArg_ = configurationFile;
244 ReadConfiguration(configuration_, configurationFile);
245
246 // Adapt the paths to the configurations
247 defaultDirectory_ = boost::filesystem::current_path();
248 configurationAbsolutePath_ = "";
249
250 if (configurationFile)
251 {
252 if (boost::filesystem::is_directory(configurationFile))
253 {
254 defaultDirectory_ = boost::filesystem::path(configurationFile);
255 configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string();
256 }
257 else
258 {
259 defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path();
260 configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string();
261 }
262 }
263 else
264 {
265 #if ORTHANC_STANDALONE != 1
266 // In a non-standalone build, we use the
267 // "Resources/Configuration.json" from the Orthanc source code
268
269 boost::filesystem::path p = ORTHANC_PATH;
270 p /= "Resources";
271 p /= "Configuration.json";
272 configurationAbsolutePath_ = boost::filesystem::absolute(p).string();
273 #endif
274 }
275 }
276
277
278 static void ValidateGlobalConfiguration()
279 {
280 std::set<std::string> ids;
281
282 Configuration::GetListOfOrthancPeers(ids);
283 for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it)
284 {
285 WebServiceParameters peer;
286 Configuration::GetOrthancPeer(peer, *it);
287 peer.CheckClientCertificate();
288 }
289
290 Configuration::GetListOfDicomModalities(ids);
291 for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it)
292 {
293 RemoteModalityParameters modality;
294 Configuration::GetDicomModalityUsingSymbolicName(modality, *it);
295 }
296 }
297
298
299 static void RegisterUserMetadata()
300 {
301 if (configuration_.isMember("UserMetadata"))
302 {
303 const Json::Value& parameter = configuration_["UserMetadata"];
304 62
305 Json::Value::Members members = parameter.getMemberNames(); 63 Json::Value::Members members = parameter.getMemberNames();
306 for (size_t i = 0; i < members.size(); i++) 64 for (size_t i = 0; i < members.size(); i++)
307 { 65 {
308 const std::string& name = members[i]; 66 const std::string& name = members[i];
330 } 88 }
331 } 89 }
332 } 90 }
333 91
334 92
335 static void RegisterUserContentType() 93 static void RegisterUserContentType(const Json::Value& config)
336 { 94 {
337 if (configuration_.isMember("UserContentType")) 95 if (config.isMember("UserContentType"))
338 { 96 {
339 const Json::Value& parameter = configuration_["UserContentType"]; 97 const Json::Value& parameter = config["UserContentType"];
340 98
341 Json::Value::Members members = parameter.getMemberNames(); 99 Json::Value::Members members = parameter.getMemberNames();
342 for (size_t i = 0; i < members.size(); i++) 100 for (size_t i = 0; i < members.size(); i++)
343 { 101 {
344 const std::string& name = members[i]; 102 const std::string& name = members[i];
462 220
463 221
464 222
465 void OrthancInitialize(const char* configurationFile) 223 void OrthancInitialize(const char* configurationFile)
466 { 224 {
467 boost::recursive_mutex::scoped_lock lock(globalMutex_); 225 OrthancConfiguration::WriterLock lock;
468 226
469 Toolbox::InitializeOpenSsl(); 227 Toolbox::InitializeOpenSsl();
470 228
471 InitializeServerEnumerations(); 229 InitializeServerEnumerations();
472 230
473 // Read the user-provided configuration 231 // Read the user-provided configuration
474 ReadGlobalConfiguration(configurationFile); 232 lock.GetConfiguration().Read(configurationFile);
475 ValidateGlobalConfiguration(); 233
476 234 if (lock.GetJson().isMember("Locale"))
477 if (configuration_.isMember("Locale")) 235 {
478 { 236 std::string locale = lock.GetConfiguration().GetStringParameter("Locale", "");
479 std::string locale = GetGlobalStringParameterInternal("Locale", ""); 237 Toolbox::InitializeGlobalLocale(lock.GetJson()["Locale"].asCString());
480 Toolbox::InitializeGlobalLocale(configuration_["Locale"].asCString());
481 } 238 }
482 else 239 else
483 { 240 {
484 Toolbox::InitializeGlobalLocale(NULL); 241 Toolbox::InitializeGlobalLocale(NULL);
485 } 242 }
486 243
487 if (configuration_.isMember("DefaultEncoding")) 244 if (lock.GetJson().isMember("DefaultEncoding"))
488 { 245 {
489 std::string encoding = GetGlobalStringParameterInternal("DefaultEncoding", ""); 246 std::string encoding = lock.GetConfiguration().GetStringParameter("DefaultEncoding", "");
490 SetDefaultDicomEncoding(StringToEncoding(encoding.c_str())); 247 SetDefaultDicomEncoding(StringToEncoding(encoding.c_str()));
491 } 248 }
492 else 249 else
493 { 250 {
494 SetDefaultDicomEncoding(ORTHANC_DEFAULT_DICOM_ENCODING); 251 SetDefaultDicomEncoding(ORTHANC_DEFAULT_DICOM_ENCODING);
495 } 252 }
496 253
497 if (configuration_.isMember("Pkcs11")) 254 if (lock.GetJson().isMember("Pkcs11"))
498 { 255 {
499 ConfigurePkcs11(configuration_["Pkcs11"]); 256 ConfigurePkcs11(lock.GetJson()["Pkcs11"]);
500 } 257 }
501 258
502 HttpClient::GlobalInitialize(); 259 HttpClient::GlobalInitialize();
503 260
504 RegisterUserMetadata(); 261 RegisterUserMetadata(lock.GetJson());
505 RegisterUserContentType(); 262 RegisterUserContentType(lock.GetJson());
506 263
507 FromDcmtkBridge::InitializeDictionary(GetGlobalBoolParameterInternal("LoadPrivateDictionary", true)); 264 bool loadPrivate = lock.GetConfiguration().GetBooleanParameter("LoadPrivateDictionary", true);
508 LoadCustomDictionary(configuration_); 265 FromDcmtkBridge::InitializeDictionary(loadPrivate);
266 LoadCustomDictionary(lock.GetJson());
509 267
510 FromDcmtkBridge::InitializeCodecs(); 268 FromDcmtkBridge::InitializeCodecs();
511 269
512 fontRegistry_.AddFromResource(EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); 270 lock.GetConfiguration().RegisterFont(EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
513 271
514 /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ 272 /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */
515 dcmDisableGethostbyaddr.set(OFTrue); 273 dcmDisableGethostbyaddr.set(OFTrue);
516 } 274 }
517 275
518 276
519 277
520 void OrthancFinalize() 278 void OrthancFinalize()
521 { 279 {
522 boost::recursive_mutex::scoped_lock lock(globalMutex_); 280 OrthancConfiguration::WriterLock lock;
281
523 HttpClient::GlobalFinalize(); 282 HttpClient::GlobalFinalize();
524 FromDcmtkBridge::FinalizeCodecs(); 283 FromDcmtkBridge::FinalizeCodecs();
525 Toolbox::FinalizeOpenSsl(); 284 Toolbox::FinalizeOpenSsl();
526 Toolbox::FinalizeGlobalLocale(); 285 Toolbox::FinalizeGlobalLocale();
527 } 286 }
528 287
529 288
530 std::string Configuration::GetGlobalStringParameter(const std::string& parameter,
531 const std::string& defaultValue)
532 {
533 boost::recursive_mutex::scoped_lock lock(globalMutex_);
534 return GetGlobalStringParameterInternal(parameter, defaultValue);
535 }
536
537
538 int Configuration::GetGlobalIntegerParameter(const std::string& parameter,
539 int defaultValue)
540 {
541 boost::recursive_mutex::scoped_lock lock(globalMutex_);
542
543 if (configuration_.isMember(parameter))
544 {
545 if (configuration_[parameter].type() != Json::intValue)
546 {
547 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be an integer";
548 throw OrthancException(ErrorCode_BadParameterType);
549 }
550 else
551 {
552 return configuration_[parameter].asInt();
553 }
554 }
555 else
556 {
557 return defaultValue;
558 }
559 }
560
561
562 unsigned int Configuration::GetGlobalUnsignedIntegerParameter(const std::string& parameter,
563 unsigned int defaultValue)
564 {
565 int v = GetGlobalIntegerParameter(parameter, defaultValue);
566
567 if (v < 0)
568 {
569 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a positive integer";
570 throw OrthancException(ErrorCode_ParameterOutOfRange);
571 }
572 else
573 {
574 return static_cast<unsigned int>(v);
575 }
576 }
577
578
579 bool Configuration::GetGlobalBoolParameter(const std::string& parameter,
580 bool defaultValue)
581 {
582 boost::recursive_mutex::scoped_lock lock(globalMutex_);
583 return GetGlobalBoolParameterInternal(parameter, defaultValue);
584 }
585
586
587 void Configuration::GetDicomModalityUsingSymbolicName(RemoteModalityParameters& modality,
588 const std::string& name)
589 {
590 boost::recursive_mutex::scoped_lock lock(globalMutex_);
591
592 if (!configuration_.isMember("DicomModalities"))
593 {
594 LOG(ERROR) << "No modality with symbolic name: " << name;
595 throw OrthancException(ErrorCode_InexistentItem);
596 }
597
598 const Json::Value& modalities = configuration_["DicomModalities"];
599 if (modalities.type() != Json::objectValue ||
600 !modalities.isMember(name))
601 {
602 LOG(ERROR) << "No modality with symbolic name: " << name;
603 throw OrthancException(ErrorCode_InexistentItem);
604 }
605
606 try
607 {
608 modality.Unserialize(modalities[name]);
609 }
610 catch (OrthancException&)
611 {
612 LOG(ERROR) << "Syntax error in the definition of DICOM modality \"" << name
613 << "\". Please check your configuration file.";
614 throw;
615 }
616 }
617
618
619 bool Configuration::GetOrthancPeer(WebServiceParameters& peer,
620 const std::string& name)
621 {
622 boost::recursive_mutex::scoped_lock lock(globalMutex_);
623
624 if (!configuration_.isMember("OrthancPeers"))
625 {
626 return false;
627 }
628
629 try
630 {
631 const Json::Value& modalities = configuration_["OrthancPeers"];
632 if (modalities.type() != Json::objectValue ||
633 !modalities.isMember(name))
634 {
635 return false;
636 }
637 else
638 {
639 peer.Unserialize(modalities[name]);
640 return true;
641 }
642 }
643 catch (OrthancException&)
644 {
645 LOG(ERROR) << "Syntax error in the definition of peer \"" << name
646 << "\". Please check your configuration file.";
647 throw;
648 }
649 }
650
651
652 static bool ReadKeys(std::set<std::string>& target,
653 const char* parameter,
654 bool onlyAlphanumeric)
655 {
656 boost::recursive_mutex::scoped_lock lock(globalMutex_);
657
658 target.clear();
659
660 if (!configuration_.isMember(parameter))
661 {
662 return true;
663 }
664
665 const Json::Value& modalities = configuration_[parameter];
666 if (modalities.type() != Json::objectValue)
667 {
668 LOG(ERROR) << "Bad format of the \"DicomModalities\" configuration section";
669 throw OrthancException(ErrorCode_BadFileFormat);
670 }
671
672 Json::Value::Members members = modalities.getMemberNames();
673 for (size_t i = 0; i < members.size(); i++)
674 {
675 if (onlyAlphanumeric)
676 {
677 for (size_t j = 0; j < members[i].size(); j++)
678 {
679 if (!isalnum(members[i][j]) && members[i][j] != '-')
680 {
681 return false;
682 }
683 }
684 }
685
686 target.insert(members[i]);
687 }
688
689 return true;
690 }
691
692
693 void Configuration::GetListOfDicomModalities(std::set<std::string>& target)
694 {
695 target.clear();
696
697 if (!ReadKeys(target, "DicomModalities", true))
698 {
699 LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of the modalities";
700 throw OrthancException(ErrorCode_BadFileFormat);
701 }
702 }
703
704
705 void Configuration::GetListOfOrthancPeers(std::set<std::string>& target)
706 {
707 target.clear();
708
709 if (!ReadKeys(target, "OrthancPeers", true))
710 {
711 LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of Orthanc peers";
712 throw OrthancException(ErrorCode_BadFileFormat);
713 }
714 }
715
716
717
718 void Configuration::SetupRegisteredUsers(MongooseServer& httpServer)
719 {
720 boost::recursive_mutex::scoped_lock lock(globalMutex_);
721
722 httpServer.ClearUsers();
723
724 if (!configuration_.isMember("RegisteredUsers"))
725 {
726 return;
727 }
728
729 const Json::Value& users = configuration_["RegisteredUsers"];
730 if (users.type() != Json::objectValue)
731 {
732 LOG(ERROR) << "Badly formatted list of users";
733 throw OrthancException(ErrorCode_BadFileFormat);
734 }
735
736 Json::Value::Members usernames = users.getMemberNames();
737 for (size_t i = 0; i < usernames.size(); i++)
738 {
739 const std::string& username = usernames[i];
740 std::string password = users[username].asString();
741 httpServer.RegisterUser(username.c_str(), password.c_str());
742 }
743 }
744
745
746 std::string Configuration::InterpretStringParameterAsPath(const std::string& parameter)
747 {
748 boost::recursive_mutex::scoped_lock lock(globalMutex_);
749 return SystemToolbox::InterpretRelativePath(defaultDirectory_.string(), parameter);
750 }
751
752
753 void Configuration::GetGlobalListOfStringsParameter(std::list<std::string>& target,
754 const std::string& key)
755 {
756 boost::recursive_mutex::scoped_lock lock(globalMutex_);
757
758 target.clear();
759
760 if (!configuration_.isMember(key))
761 {
762 return;
763 }
764
765 const Json::Value& lst = configuration_[key];
766
767 if (lst.type() != Json::arrayValue)
768 {
769 LOG(ERROR) << "Badly formatted list of strings";
770 throw OrthancException(ErrorCode_BadFileFormat);
771 }
772
773 for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
774 {
775 target.push_back(lst[i].asString());
776 }
777 }
778
779
780 bool Configuration::IsSameAETitle(const std::string& aet1,
781 const std::string& aet2)
782 {
783 if (GetGlobalBoolParameter("StrictAetComparison", false))
784 {
785 // Case-sensitive matching
786 return aet1 == aet2;
787 }
788 else
789 {
790 // Case-insensitive matching (default)
791 std::string tmp1, tmp2;
792 Toolbox::ToLowerCase(tmp1, aet1);
793 Toolbox::ToLowerCase(tmp2, aet2);
794 return tmp1 == tmp2;
795 }
796 }
797
798
799 bool Configuration::LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality,
800 const std::string& aet)
801 {
802 std::set<std::string> modalities;
803 GetListOfDicomModalities(modalities);
804
805 for (std::set<std::string>::const_iterator
806 it = modalities.begin(); it != modalities.end(); ++it)
807 {
808 try
809 {
810 GetDicomModalityUsingSymbolicName(modality, *it);
811
812 if (IsSameAETitle(aet, modality.GetApplicationEntityTitle()))
813 {
814 return true;
815 }
816 }
817 catch (OrthancException&)
818 {
819 }
820 }
821
822 return false;
823 }
824
825
826 bool Configuration::IsKnownAETitle(const std::string& aet,
827 const std::string& ip)
828 {
829 RemoteModalityParameters modality;
830
831 if (!LookupDicomModalityUsingAETitle(modality, aet))
832 {
833 LOG(WARNING) << "Modality \"" << aet
834 << "\" is not listed in the \"DicomModalities\" configuration option";
835 return false;
836 }
837 else if (!Configuration::GetGlobalBoolParameter("DicomCheckModalityHost", false) ||
838 ip == modality.GetHost())
839 {
840 return true;
841 }
842 else
843 {
844 LOG(WARNING) << "Forbidding access from AET \"" << aet
845 << "\" given its hostname (" << ip << ") does not match "
846 << "the \"DicomModalities\" configuration option ("
847 << modality.GetHost() << " was expected)";
848 return false;
849 }
850 }
851
852
853 RemoteModalityParameters Configuration::GetModalityUsingSymbolicName(const std::string& name)
854 {
855 RemoteModalityParameters modality;
856 GetDicomModalityUsingSymbolicName(modality, name);
857
858 return modality;
859 }
860
861
862 RemoteModalityParameters Configuration::GetModalityUsingAet(const std::string& aet)
863 {
864 RemoteModalityParameters modality;
865
866 if (LookupDicomModalityUsingAETitle(modality, aet))
867 {
868 return modality;
869 }
870 else
871 {
872 LOG(ERROR) << "Unknown modality for AET: " << aet;
873 throw OrthancException(ErrorCode_InexistentItem);
874 }
875 }
876
877
878 void Configuration::UpdateModality(ServerContext& context,
879 const std::string& symbolicName,
880 const RemoteModalityParameters& modality)
881 {
882 {
883 boost::recursive_mutex::scoped_lock lock(globalMutex_);
884
885 if (!configuration_.isMember("DicomModalities"))
886 {
887 configuration_["DicomModalities"] = Json::objectValue;
888 }
889
890 Json::Value& modalities = configuration_["DicomModalities"];
891 if (modalities.type() != Json::objectValue)
892 {
893 LOG(ERROR) << "Bad file format for modality: " << symbolicName;
894 throw OrthancException(ErrorCode_BadFileFormat);
895 }
896
897 modalities.removeMember(symbolicName);
898
899 Json::Value v;
900 modality.Serialize(v, true /* force advanced format */);
901 modalities[symbolicName] = v;
902 }
903
904 #if ORTHANC_ENABLE_PLUGINS == 1
905 if (context.HasPlugins())
906 {
907 context.GetPlugins().SignalUpdatedModalities();
908 }
909 #endif
910 }
911
912
913 void Configuration::RemoveModality(ServerContext& context,
914 const std::string& symbolicName)
915 {
916 {
917 boost::recursive_mutex::scoped_lock lock(globalMutex_);
918
919 if (!configuration_.isMember("DicomModalities"))
920 {
921 LOG(ERROR) << "No modality with symbolic name: " << symbolicName;
922 throw OrthancException(ErrorCode_BadFileFormat);
923 }
924
925 Json::Value& modalities = configuration_["DicomModalities"];
926 if (modalities.type() != Json::objectValue)
927 {
928 LOG(ERROR) << "Bad file format for the \"DicomModalities\" configuration section";
929 throw OrthancException(ErrorCode_BadFileFormat);
930 }
931
932 modalities.removeMember(symbolicName.c_str());
933 }
934
935 #if ORTHANC_ENABLE_PLUGINS == 1
936 if (context.HasPlugins())
937 {
938 context.GetPlugins().SignalUpdatedModalities();
939 }
940 #endif
941 }
942
943
944 void Configuration::UpdatePeer(ServerContext& context,
945 const std::string& symbolicName,
946 const WebServiceParameters& peer)
947 {
948 peer.CheckClientCertificate();
949
950 {
951 boost::recursive_mutex::scoped_lock lock(globalMutex_);
952
953 if (!configuration_.isMember("OrthancPeers"))
954 {
955 LOG(ERROR) << "No peer with symbolic name: " << symbolicName;
956 configuration_["OrthancPeers"] = Json::objectValue;
957 }
958
959 Json::Value& peers = configuration_["OrthancPeers"];
960 if (peers.type() != Json::objectValue)
961 {
962 LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section";
963 throw OrthancException(ErrorCode_BadFileFormat);
964 }
965
966 peers.removeMember(symbolicName);
967
968 Json::Value v;
969 peer.Serialize(v,
970 false /* use simple format if possible */,
971 true /* include passwords */);
972 peers[symbolicName] = v;
973 }
974
975 #if ORTHANC_ENABLE_PLUGINS == 1
976 if (context.HasPlugins())
977 {
978 context.GetPlugins().SignalUpdatedPeers();
979 }
980 #endif
981 }
982
983
984 void Configuration::RemovePeer(ServerContext& context,
985 const std::string& symbolicName)
986 {
987 {
988 boost::recursive_mutex::scoped_lock lock(globalMutex_);
989
990 if (!configuration_.isMember("OrthancPeers"))
991 {
992 LOG(ERROR) << "No peer with symbolic name: " << symbolicName;
993 throw OrthancException(ErrorCode_BadFileFormat);
994 }
995
996 Json::Value& peers = configuration_["OrthancPeers"];
997 if (peers.type() != Json::objectValue)
998 {
999 LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section";
1000 throw OrthancException(ErrorCode_BadFileFormat);
1001 }
1002
1003 peers.removeMember(symbolicName.c_str());
1004 }
1005
1006 #if ORTHANC_ENABLE_PLUGINS == 1
1007 if (context.HasPlugins())
1008 {
1009 context.GetPlugins().SignalUpdatedPeers();
1010 }
1011 #endif
1012 }
1013
1014
1015
1016 const std::string& Configuration::GetConfigurationAbsolutePath()
1017 {
1018 return configurationAbsolutePath_;
1019 }
1020
1021
1022 static IDatabaseWrapper* CreateSQLiteWrapper() 289 static IDatabaseWrapper* CreateSQLiteWrapper()
1023 { 290 {
1024 std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); 291 OrthancConfiguration::ReaderLock lock;
292
293 std::string storageDirectoryStr =
294 lock.GetConfiguration().GetStringParameter("StorageDirectory", "OrthancStorage");
1025 295
1026 // Open the database 296 // Open the database
1027 boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath( 297 boost::filesystem::path indexDirectory = lock.GetConfiguration().InterpretStringParameterAsPath(
1028 Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); 298 lock.GetConfiguration().GetStringParameter("IndexDirectory", storageDirectoryStr));
1029 299
1030 LOG(WARNING) << "SQLite index directory: " << indexDirectory; 300 LOG(WARNING) << "SQLite index directory: " << indexDirectory;
1031 301
1032 try 302 try
1033 { 303 {
1092 } 362 }
1093 363
1094 364
1095 static IStorageArea* CreateFilesystemStorage() 365 static IStorageArea* CreateFilesystemStorage()
1096 { 366 {
1097 std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); 367 OrthancConfiguration::ReaderLock lock;
1098 368
1099 boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr); 369 std::string storageDirectoryStr =
370 lock.GetConfiguration().GetStringParameter("StorageDirectory", "OrthancStorage");
371
372 boost::filesystem::path storageDirectory =
373 lock.GetConfiguration().InterpretStringParameterAsPath(storageDirectoryStr);
374
1100 LOG(WARNING) << "Storage directory: " << storageDirectory; 375 LOG(WARNING) << "Storage directory: " << storageDirectory;
1101 376
1102 if (Configuration::GetGlobalBoolParameter("StoreDicom", true)) 377 if (lock.GetConfiguration().GetBooleanParameter("StoreDicom", true))
1103 { 378 {
1104 return new FilesystemStorage(storageDirectory.string()); 379 return new FilesystemStorage(storageDirectory.string());
1105 } 380 }
1106 else 381 else
1107 { 382 {
1109 return new FilesystemStorageWithoutDicom(storageDirectory.string()); 384 return new FilesystemStorageWithoutDicom(storageDirectory.string());
1110 } 385 }
1111 } 386 }
1112 387
1113 388
1114 IDatabaseWrapper* Configuration::CreateDatabaseWrapper() 389 IDatabaseWrapper* CreateDatabaseWrapper()
1115 { 390 {
1116 return CreateSQLiteWrapper(); 391 return CreateSQLiteWrapper();
1117 } 392 }
1118 393
1119 394
1120 IStorageArea* Configuration::CreateStorageArea() 395 IStorageArea* CreateStorageArea()
1121 { 396 {
1122 return CreateFilesystemStorage(); 397 return CreateFilesystemStorage();
1123 } 398 }
1124
1125
1126 void Configuration::GetConfiguration(Json::Value& result)
1127 {
1128 boost::recursive_mutex::scoped_lock lock(globalMutex_);
1129 result = configuration_;
1130 }
1131
1132
1133 void Configuration::FormatConfiguration(std::string& result)
1134 {
1135 Json::Value config;
1136 GetConfiguration(config);
1137
1138 Json::StyledWriter w;
1139 result = w.write(config);
1140 }
1141
1142
1143 const FontRegistry& Configuration::GetFontRegistry()
1144 {
1145 return fontRegistry_;
1146 }
1147
1148
1149 void Configuration::SetDefaultEncoding(Encoding encoding)
1150 {
1151 SetDefaultDicomEncoding(encoding);
1152
1153 {
1154 // Propagate the encoding to the configuration file that is
1155 // stored in memory
1156 boost::recursive_mutex::scoped_lock lock(globalMutex_);
1157 configuration_["DefaultEncoding"] = EnumerationToString(encoding);
1158 }
1159 }
1160
1161
1162 bool Configuration::HasConfigurationChanged()
1163 {
1164 Json::Value starting;
1165 GetConfiguration(starting);
1166
1167 Json::Value current;
1168 ReadConfiguration(current, configurationFileArg_);
1169
1170 Json::FastWriter writer;
1171 std::string a = writer.write(starting);
1172 std::string b = writer.write(current);
1173
1174 return a != b;
1175 }
1176 } 399 }