1 /******************************************************************************
3 * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "base/configobject.hpp"
21 #include "base/configobject.tcpp"
22 #include "base/configtype.hpp"
23 #include "base/serializer.hpp"
24 #include "base/netstring.hpp"
25 #include "base/json.hpp"
26 #include "base/stdiostream.hpp"
27 #include "base/debug.hpp"
28 #include "base/objectlock.hpp"
29 #include "base/logger.hpp"
30 #include "base/exception.hpp"
31 #include "base/function.hpp"
32 #include "base/initialize.hpp"
33 #include "base/workqueue.hpp"
34 #include "base/context.hpp"
35 #include "base/application.hpp"
36 #include "config/configitem.hpp"
38 #include <boost/foreach.hpp>
39 #include <boost/algorithm/string/split.hpp>
40 #include <boost/algorithm/string/classification.hpp>
41 #include <boost/exception/errinfo_api_function.hpp>
42 #include <boost/exception/errinfo_errno.hpp>
43 #include <boost/exception/errinfo_file_name.hpp>
45 using namespace icinga;
47 REGISTER_TYPE_WITH_PROTOTYPE(ConfigObject, ConfigObject::GetPrototype());
49 boost::signals2::signal<void (const ConfigObject::Ptr&)> ConfigObject::OnStateChanged;
51 ConfigObject::ConfigObject(void)
54 ConfigType::Ptr ConfigObject::GetType(void) const
56 return ConfigType::GetByName(GetTypeNameV());
59 bool ConfigObject::IsActive(void) const
64 bool ConfigObject::IsPaused(void) const
69 void ConfigObject::SetExtension(const String& key, const Value& value)
71 Dictionary::Ptr extensions = GetExtensions();
74 extensions = new Dictionary();
75 SetExtensions(extensions);
78 extensions->Set(key, value);
81 Value ConfigObject::GetExtension(const String& key)
83 Dictionary::Ptr extensions = GetExtensions();
88 return extensions->Get(key);
91 void ConfigObject::ClearExtension(const String& key)
93 Dictionary::Ptr extensions = GetExtensions();
98 extensions->Remove(key);
101 class ModAttrValidationUtils : public ValidationUtils
104 virtual bool ValidateName(const String& type, const String& name) const override
106 ConfigType::Ptr dtype = ConfigType::GetByName(type);
111 if (!dtype->GetObject(name))
118 void ConfigObject::ModifyAttribute(const String& attr, const Value& value, bool updateVersion)
120 Dictionary::Ptr original_attributes = GetOriginalAttributes();
121 bool updated_original_attributes = false;
123 Type::Ptr type = GetReflectionType();
125 std::vector<String> tokens;
126 boost::algorithm::split(tokens, attr, boost::is_any_of("."));
128 String fieldName = tokens[0];
130 int fid = type->GetFieldId(fieldName);
131 Field field = type->GetFieldInfo(fid);
133 if (field.Attributes & FANoUserModify)
134 BOOST_THROW_EXCEPTION(std::invalid_argument("Attribute cannot be modified."));
136 if (field.Attributes & FAConfig) {
137 if (!original_attributes) {
138 original_attributes = new Dictionary();
139 SetOriginalAttributes(original_attributes, true);
143 Value oldValue = GetField(fid);
146 if (tokens.size() > 1) {
147 newValue = oldValue.Clone();
148 Value current = newValue;
150 if (current.IsEmpty()) {
151 current = new Dictionary();
155 String prefix = tokens[0];
157 for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
158 if (!current.IsObjectType<Dictionary>())
159 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
161 Dictionary::Ptr dict = current;
163 const String& key = tokens[i];
166 if (!dict->Contains(key)) {
167 current = new Dictionary();
168 dict->Set(key, current);
170 current = dict->Get(key);
174 if (!current.IsObjectType<Dictionary>())
175 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
177 Dictionary::Ptr dict = current;
179 const String& key = tokens[tokens.size() - 1];
182 /* clone it for original attributes */
183 oldValue = dict->Get(key).Clone();
185 if (field.Attributes & FAConfig) {
186 updated_original_attributes = true;
188 if (oldValue.IsObjectType<Dictionary>()) {
189 Dictionary::Ptr oldDict = oldValue;
190 ObjectLock olock(oldDict);
191 BOOST_FOREACH(const Dictionary::Pair& kv, oldDict) {
192 String key = prefix + "." + kv.first;
193 if (!original_attributes->Contains(key))
194 original_attributes->Set(key, kv.second);
197 /* store the new value as null */
198 if (value.IsObjectType<Dictionary>()) {
199 Dictionary::Ptr valueDict = value;
200 ObjectLock olock(valueDict);
201 BOOST_FOREACH(const Dictionary::Pair& kv, valueDict) {
202 String key = attr + "." + kv.first;
203 if (!original_attributes->Contains(key))
204 original_attributes->Set(key, Empty);
207 } else if (!original_attributes->Contains(attr))
208 original_attributes->Set(attr, oldValue);
211 dict->Set(key, value);
215 if (field.Attributes & FAConfig) {
216 if (!original_attributes->Contains(attr)) {
217 updated_original_attributes = true;
218 original_attributes->Set(attr, oldValue);
223 ModAttrValidationUtils utils;
224 ValidateField(fid, newValue, utils);
226 SetField(fid, newValue);
228 if (updateVersion && (field.Attributes & FAConfig))
229 SetVersion(Utility::GetTime());
231 if (updated_original_attributes)
232 NotifyOriginalAttributes();
235 void ConfigObject::RestoreAttribute(const String& attr)
237 Type::Ptr type = GetReflectionType();
239 std::vector<String> tokens;
240 boost::algorithm::split(tokens, attr, boost::is_any_of("."));
242 String fieldName = tokens[0];
244 int fid = type->GetFieldId(fieldName);
245 Field field = type->GetFieldInfo(fid);
247 Value currentValue = GetField(fid);
249 Dictionary::Ptr original_attributes = GetOriginalAttributes();
251 if (!original_attributes)
254 Value oldValue = original_attributes->Get(attr);
257 if (tokens.size() > 1) {
258 newValue = currentValue.Clone();
259 Value current = newValue;
261 if (current.IsEmpty())
262 BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existing object attribute"));
264 String prefix = tokens[0];
266 for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
267 if (!current.IsObjectType<Dictionary>())
268 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
270 Dictionary::Ptr dict = current;
272 const String& key = tokens[i];
275 if (!dict->Contains(key))
276 BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existing object attribute"));
278 current = dict->Get(key);
281 if (!current.IsObjectType<Dictionary>())
282 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
284 Dictionary::Ptr dict = current;
286 const String& key = tokens[tokens.size() - 1];
289 std::vector<String> restoredAttrs;
292 ObjectLock olock(original_attributes);
293 BOOST_FOREACH(const Dictionary::Pair& kv, original_attributes) {
294 std::vector<String> originalTokens;
295 boost::algorithm::split(originalTokens, kv.first, boost::is_any_of("."));
297 if (tokens.size() > originalTokens.size())
301 for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) {
302 if (tokens[i] != originalTokens[i]) {
311 Dictionary::Ptr dict;
313 if (tokens.size() == originalTokens.size())
316 Value currentSub = current;
318 for (std::vector<String>::size_type i = tokens.size() - 1; i < originalTokens.size() - 1; i++) {
320 currentSub = dict->Get(originalTokens[i]);
322 if (!currentSub.IsObjectType<Dictionary>()) {
323 currentSub = new Dictionary();
324 dict->Set(originalTokens[i], currentSub);
331 dict->Set(originalTokens[originalTokens.size() - 1], kv.second);
332 restoredAttrs.push_back(kv.first);
336 BOOST_FOREACH(const String& attr, restoredAttrs)
337 original_attributes->Remove(attr);
344 original_attributes->Remove(attr);
345 SetField(fid, newValue);
347 SetVersion(Utility::GetTime());
350 bool ConfigObject::IsAttributeModified(const String& attr) const
352 Dictionary::Ptr original_attributes = GetOriginalAttributes();
354 if (!original_attributes)
357 return original_attributes->Contains(attr);
360 void ConfigObject::Register(void)
364 ConfigType::Ptr dtype = GetType();
365 dtype->RegisterObject(this);
368 void ConfigObject::Unregister(void)
372 ConfigType::Ptr dtype = GetType();
373 dtype->UnregisterObject(this);
376 void ConfigObject::Start(void)
378 ObjectImpl<ConfigObject>::Start();
381 ObjectLock olock(this);
383 SetStartCalled(true);
386 void ConfigObject::Activate(void)
388 CONTEXT("Activating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
394 ASSERT(GetStartCalled());
397 ObjectLock olock(this);
399 SetActive(true, true);
407 void ConfigObject::Stop(void)
409 ObjectImpl<ConfigObject>::Stop();
412 ObjectLock olock(this);
417 void ConfigObject::Deactivate(void)
419 CONTEXT("Deactivating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
426 ObjectLock olock(this);
431 SetActive(false, true);
436 ASSERT(GetStopCalled());
441 void ConfigObject::OnConfigLoaded(void)
443 /* Nothing to do here. */
446 void ConfigObject::OnAllConfigLoaded(void)
448 /* Nothing to do here. */
451 void ConfigObject::CreateChildObjects(const Type::Ptr& childType)
453 /* Nothing to do here. */
456 void ConfigObject::OnStateLoaded(void)
458 /* Nothing to do here. */
461 void ConfigObject::Pause(void)
463 SetPauseCalled(true);
466 void ConfigObject::Resume(void)
468 SetResumeCalled(true);
471 void ConfigObject::SetAuthority(bool authority)
473 if (authority && GetPaused()) {
474 SetResumeCalled(false);
476 ASSERT(GetResumeCalled());
478 } else if (!authority && !GetPaused()) {
479 SetPauseCalled(false);
481 ASSERT(GetPauseCalled());
486 void ConfigObject::DumpObjects(const String& filename, int attributeTypes)
488 Log(LogInformation, "ConfigObject")
489 << "Dumping program state to file '" << filename << "'";
491 String tempFilename = filename + ".tmp";
494 fp.open(tempFilename.CStr(), std::ios_base::out);
497 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open '" + tempFilename + "' file"));
499 StdioStream::Ptr sfp = new StdioStream(&fp, false);
501 BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
502 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
503 Dictionary::Ptr persistentObject = new Dictionary();
505 persistentObject->Set("type", type->GetName());
506 persistentObject->Set("name", object->GetName());
508 Dictionary::Ptr update = Serialize(object, attributeTypes);
513 persistentObject->Set("update", update);
515 String json = JsonEncode(persistentObject);
517 NetString::WriteStringToStream(sfp, json);
526 _unlink(filename.CStr());
529 if (rename(tempFilename.CStr(), filename.CStr()) < 0) {
530 BOOST_THROW_EXCEPTION(posix_error()
531 << boost::errinfo_api_function("rename")
532 << boost::errinfo_errno(errno)
533 << boost::errinfo_file_name(tempFilename));
537 void ConfigObject::RestoreObject(const String& message, int attributeTypes)
539 Dictionary::Ptr persistentObject = JsonDecode(message);
541 String type = persistentObject->Get("type");
543 ConfigType::Ptr dt = ConfigType::GetByName(type);
548 String name = persistentObject->Get("name");
550 ConfigObject::Ptr object = dt->GetObject(name);
555 ASSERT(!object->IsActive());
557 Log(LogDebug, "ConfigObject")
558 << "Restoring object '" << name << "' of type '" << type << "'.";
559 #endif /* I2_DEBUG */
560 Dictionary::Ptr update = persistentObject->Get("update");
561 Deserialize(object, update, false, attributeTypes);
562 object->OnStateLoaded();
563 object->SetStateLoaded(true);
566 void ConfigObject::RestoreObjects(const String& filename, int attributeTypes)
568 if (!Utility::PathExists(filename))
571 Log(LogInformation, "ConfigObject")
572 << "Restoring program state from file '" << filename << "'";
575 fp.open(filename.CStr(), std::ios_base::in);
577 StdioStream::Ptr sfp = new StdioStream (&fp, false);
579 unsigned long restored = 0;
581 WorkQueue upq(25000, Application::GetConcurrency());
584 StreamReadContext src;
586 StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src);
588 if (srs == StatusEof)
591 if (srs != StatusNewItem)
594 upq.Enqueue(boost::bind(&ConfigObject::RestoreObject, message, attributeTypes));
602 unsigned long no_state = 0;
604 BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
605 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
606 if (!object->GetStateLoaded()) {
607 object->OnStateLoaded();
608 object->SetStateLoaded(true);
615 Log(LogInformation, "ConfigObject")
616 << "Restored " << restored << " objects. Loaded " << no_state << " new objects without state.";
619 void ConfigObject::StopObjects(void)
621 BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
622 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
623 object->Deactivate();
628 void ConfigObject::DumpModifiedAttributes(const boost::function<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback)
630 BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
631 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
632 Dictionary::Ptr originalAttributes = object->GetOriginalAttributes();
634 if (!originalAttributes)
637 ObjectLock olock(originalAttributes);
638 BOOST_FOREACH(const Dictionary::Pair& kv, originalAttributes) {
639 String key = kv.first;
641 Type::Ptr type = object->GetReflectionType();
643 std::vector<String> tokens;
644 boost::algorithm::split(tokens, key, boost::is_any_of("."));
646 String fieldName = tokens[0];
647 int fid = type->GetFieldId(fieldName);
649 Value currentValue = object->GetField(fid);
652 if (tokens.size() > 1) {
653 Value current = currentValue;
655 for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
656 if (!current.IsObjectType<Dictionary>())
657 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
659 Dictionary::Ptr dict = current;
660 const String& key = tokens[i];
662 if (!dict->Contains(key))
665 current = dict->Get(key);
668 if (!current.IsObjectType<Dictionary>())
669 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
671 Dictionary::Ptr dict = current;
672 const String& key = tokens[tokens.size() - 1];
674 modifiedValue = dict->Get(key);
676 modifiedValue = currentValue;
678 callback(object, key, modifiedValue);
685 ConfigObject::Ptr ConfigObject::GetObject(const String& type, const String& name)
687 ConfigType::Ptr dtype = ConfigType::GetByName(type);
689 return ConfigObject::Ptr();
690 return dtype->GetObject(name);