// -*- C++ -*- // #ifndef _datastore_ConfigArchive_h_ #define _datastore_ConfigArchive_h_ #include #include #include #include #include #include #include #include "ConfigNode.h" #include "logx/Logging.h" #include #include #include #include #include "xml_cdata.h" namespace datastore { /** * Shared base implementation for ConfigOArchive and ConfigIArchive. * This class keeps track of the config node and io streams and other * state associated with both input and output config archives. Sharing * the implementation in this class allows that state to be initialized * in before constructing the corresponding xml_iarchive and * xml_oarchive. Otherwise the xml archive constructor calls down into * the subclass methods before all of the config archive's members have * been initialized. Speaking from experience, that causes strange * behavior and errors from valgrind. For example, the xml_oarchive_impl * constructor calls a init() method to write the xml header, which in * turn calls save() to write the archive version and other attributes. * That call to save() enters the overridden method in the most-derived * class ConfigOArchive, but at that point none of the members of * ConfigOArchive have been constructed. This scheme requires that * ConfigArchive be listed as the first base class of the config archive * implementation, so that it's members have been constructed before * passing construction up to the boost xml archive implementation * template. * * When serializing with a ConfigNode as the backing store, _node is * non-null and the string streams are associated with the node's XML * archive text, from getXML(). Otherwise the ConfigArchive classes * behave like a regular xml archive, serializing to and from standard * iostreams. **/ class ConfigArchive { std::string _path_string; public: typedef boost::archive::archive_exception archive_exception; protected: ConfigNode* _node; log4cpp::Category& _log; std::vector _path; std::map _depths; std::istringstream _istring; std::ostringstream _ostring; std::istream& _in; std::ostream& _os; bool _saving_value; std::string _value; char* _action; bool _exception_pending; /** * For constructing an input or output archive against a ConfigNode. * If @p output is true, then the ConfigNode value dictionary is * first cleared in preparation for new settings. Otherwise * the input stream is prepared with the ConfigNode's XML archive * string. **/ ConfigArchive(ConfigNode* node, bool output) : _node(node), _log(log4cpp::Category::getInstance("ConfigArchive")), _in(_istring), _os(_ostring), _saving_value(false), _action(0), _exception_pending(false) { if (_node && output) _node->_values.clear(); if (_node && (!output)) _istring.str(_node->getXML()); } /** * For constructing an input archive against a regular istream * instead of a ConfigNode. **/ ConfigArchive(std::istream& in) : _node(0), _log(log4cpp::Category::getInstance("ConfigArchive")), _in(in), _os(_ostring), _saving_value(false), _action(0), _exception_pending(false) { } /** * For constructing an output archive against a regular ostream * instead of a ConfigNode. **/ ConfigArchive(std::ostream& out) : _node(0), _log(log4cpp::Category::getInstance("ConfigArchive")), _in(_istring), _os(out), _saving_value(false) { } public: // If need_non_empty is true, then it means add the root path name if // otherwise this string would be empty. In other words, we need a // name for a top-level value rather than the empty string we'd get if // we left off the root. const std::string& pathToString(bool need_non_empty = false) { _path_string.erase(); unsigned int i = !(need_non_empty && _path.size() <= 1); for ( ; i < _path.size(); ++i) { if (_path_string.length() > 0) _path_string += "/"; _path_string += _path[i]; } return _path_string; } std::istream& inputStream() { return _in; } std::ostream& outputStream() { return _os; } ConfigNode* getArchiveNode() { return _node; } }; /** * A special output archive for serializing the state of a component into * the vector of name/value pairs in a ConfigNode. Thus the constructor * requires a pointer to the ConfigNode. **/ class ConfigOArchive : public ConfigArchive, public boost::archive::xml_oarchive_impl, private boost::noncopyable { typedef boost::archive::xml_oarchive_impl base; public: /** * Construct a config output archive which writes the serialization * data to named parameters in a node as well as to xml. If @p node is * null, then this archive just serializes xml to the internal string * output stream, same as an xml output archive. The final xml text * can be retrieved with the str() method. **/ ConfigOArchive(ConfigNode* node, unsigned int flags = 0) : ConfigArchive(node, /*output*/true), base(_os, flags) { } /** * Construct a config output archive which writes to a standard output * stream instead of into a ConfigNode. **/ ConfigOArchive(std::ostream& out, unsigned int flags = 0) : ConfigArchive(out), base(_os, flags) { } std::string str() { return _ostring.str(); } ~ConfigOArchive() { } void log_exception(std::exception& e) { if (_exception_pending) return; _exception_pending = true; std::string name; if (_path.size() > 0) name = _path.back(); std::streamoff pos = _os.tellp(); std::string buffer = str(); if (buffer.length() > 64) buffer = buffer.substr(buffer.length() - 64); _log.errorStream() << "exception(" << e.what() << "): " << _action << " tag '" << name << "'" << " for path " << pathToString() << ", offset " << pos << ", text:..." << buffer << "..."; } // Anything not a name-value pair gets handled by the base class. template inline void save_override(T & t, BOOST_PFTO int v) { base::save_override(t, v); } // Override the basic_xml_oarchive implementation for serializing // name-value pairs, so that we can insert the name and // value into our values vector. template void save_override( #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING const #endif ::boost::serialization::nvp & t, int v ) { try { if (! t.name()) { _action = "saving"; _saving_value = true; boost::archive::save(* this->This(), t.const_value()); _saving_value = false; } else { // Push the name onto the path stack, and set the depth flag for // the parent path. if (_path.size() > 0) { _depths[pathToString()] = 1; } _path.push_back(t.name()); _depths[pathToString()] = 0; _saving_value = false; _action = "saving start"; this->This()->save_start(t.name()); _saving_value = true; _action = "saving value"; boost::archive::save(* this->This(), t.const_value()); _saving_value = false; if (_depths[pathToString()] == 0) { if (_node) _node->addValue (pathToString(true), _value); } _action = "saving end"; this->This()->save_end(t.name()); _path.pop_back(); } if (_node) _node->setXML(_ostring.str()); } catch (std::exception& e) { log_exception(e); throw; } } void save_override( #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING const #endif ::boost::serialization::nvp & t, int v ) { this->This()->save_start(t.name()); this->This()->end_preamble(); std::string ename; // = t.name(); ename = "xmlcdata"; std::string xmlprefix = std::string("<") + ename + ">"; std::string xml = xmlprefix + *(t.const_value()._xml) + xmlsuffix; unsigned int xlen = xml.length(); (*this) & boost::serialization::make_nvp("xmlcdatalength", xlen); put (xml.c_str()); this->This()->save_end(t.name()); } template void save(const T &t) { int i = _ostring.str().length(); // _log.debugStream() << "stream before save(): " << _os.str(); base::save(t); // _log.debugStream() << "stream after save(): " << _os.str(); if (_node && _saving_value) { _value = _ostring.str().substr(i); } } void serializeReferences(const char* name, ConfigID::vector& ids) { if (_node) _node->_references = ids; } }; class ConfigIArchiveBase : public ConfigArchive { protected: bool _loading_value; ConfigNode::values_type _values; ConfigNode::values_type::iterator _it; ConfigIArchiveBase(ConfigNode* node) : ConfigArchive(node, /*output*/false), _loading_value(false) { _values = node->_values; _it = _values.begin(); _log.debugStream() << "loading " << node->getConfigClass() << " from xml:\n" << node->getXML(); } ConfigIArchiveBase(std::istream& in) : ConfigArchive(in), _loading_value(false) { } }; /** * An input archive for serializing a config node into a component. * Really this archive is backed by the xml archive of the ConfigNode, * whose data is pulled from the node and passed in via the input string * stream. Then as elements are serialized from the archive, the node's * dictionary of elements can override some of the values. * * This archive can also just load xml from a standard input stream, same * as a boost xml input archive. It's advantage over the boost archive * is that it exposes its input stream with inputStream(), to allow for * specialized handling of the input such as embedded xml. It also gives * more informative reports about exceptions. **/ class ConfigIArchive : public ConfigIArchiveBase, public boost::archive::xml_iarchive_impl, private boost::noncopyable { typedef boost::archive::xml_iarchive_impl base; public: ConfigIArchive(ConfigNode* node, unsigned int flags = 0) : ConfigIArchiveBase(node), base(_in, flags) { } ConfigIArchive(std::istream& in, unsigned int flags = 0) : ConfigIArchiveBase(in), base(_in, flags) { } ~ConfigIArchive() { } // Anything not a name-value pair gets handled by the base class. template inline void load_override(T & t, BOOST_PFTO int v) { base::load_override(t, v); } void log_exception(std::exception& e) { if (_exception_pending) return; _exception_pending = true; std::string name; if (_path.size() > 0) name = _path.back(); std::streamoff pos = _in.tellg(); char buffer[64]; _in.read(buffer, sizeof(buffer) - 1); buffer[_in.gcount()] = '\0'; _log.errorStream() << "exception(" << e.what() << "): " << _action << " tag '" << name << "'" << " for path " << pathToString() << ", offset " << pos << ", text:..." << buffer << "..."; } template void load_override( #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING const #endif boost::serialization::nvp & t, int) { try { if (! t.name()) { _loading_value = true; _action = "loading"; boost::archive::load(* this->This(), t.value()); _loading_value = false; return; } // Push the name onto the path stack _path.push_back(t.name()); _loading_value = false; _action = "loading start"; this->This()->load_start(t.name()); _loading_value = true; _action = "loading value"; boost::archive::load(* this->This(), t.value()); _loading_value = false; _action = "loading end"; this->This()->load_end(t.name()); _path.pop_back(); } catch (std::exception& e) { log_exception(e); throw; } } void load_override( #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING const #endif ::boost::serialization::nvp & t, int v ) { this->This()->load_start(t.name()); std::string ename; // = t.name(); ename = "xmlcdata"; std::string xmlprefix = std::string("<") + ename + ">"; std::string xml; unsigned int xlen; (*this) & boost::serialization::make_nvp("xmlcdatalength", xlen); boost::scoped_array xdata(new char[xlen]); _in.read(xdata.get(), xlen); if (_in.fail()) { throw archive_exception(archive_exception::stream_error); } const_cast(*(t.value()._xml)).assign (xdata.get() + xmlprefix.length(), xdata.get() + xlen - xmlsuffix.length()); _log.debugStream() << "loaded xml archive string: \n" << *t.value()._xml; this->This()->load_end(t.name()); } template void load(T &t) { // Load the primitive, but then reload the value from the // dictionary if it exists. base::load(t); if (!_node) { return; } if (_loading_value) { std::string name = pathToString(true); // If this path matches the next on one the list, take it. if (_it != _values.end() && (_it->first == name)) { std::string value = _it->second; ++_it; _log.debugStream() << "got " << name << "='" << value << "'"; // After all the above, it still doesn't work for strings. // So resort to lexical_cast. Since we're not doing any fancy // code conversion, this should still work. t = boost::lexical_cast(value); } else { _log.debugStream() << "no match for " << name << ", next element is " << (_it != _values.end() ? _it->first : "missing"); } } } void serializeReferences(const char* name, ConfigID::vector& ids) { if (_node) ids = _node->_references; } }; template inline ConfigNode* getArchiveNode(Archive& ar) { return 0; } template <> inline ConfigNode* getArchiveNode(ConfigIArchive& ar) { return ar.getArchiveNode(); } template <> inline ConfigNode* getArchiveNode(ConfigOArchive& ar) { return ar.getArchiveNode(); } template inline void serializeReferences(Archive& ar, const char* name, ConfigID::vector& ids) { } template <> inline void serializeReferences(ConfigIArchive& ar, const char* name, ConfigID::vector& ids) { ar.serializeReferences(name, ids); } template <> inline void serializeReferences(ConfigOArchive& ar, const char* name, ConfigID::vector& ids) { ar.serializeReferences(name, ids); } } // namespace datastore #include #include #include #include #include #include #define NO_ARCHIVE_CONSTRUCTOR(CLASS) \ namespace boost { namespace serialization { \ template inline void load_construct_data \ (Archive & ar, CLASS * t, const unsigned int file_version){ \ ELOG << "constructor attempted for " #CLASS; \ throw datastore::ConfigArchive::archive_exception \ (datastore::ConfigArchive::archive_exception::stream_error); } }} #define BOOST_ARCHIVE_CUSTOM_IARCHIVE_TYPES datastore::ConfigIArchive #define BOOST_ARCHIVE_CUSTOM_OARCHIVE_TYPES datastore::ConfigOArchive #endif // _datastore_ConfigArchive_h_