Belle II Software  release-05-02-19
Gearbox.cc
1 #include <framework/gearbox/Gearbox.h>
2 #include <framework/gearbox/GearDir.h>
3 #include <framework/core/MRUCache.h>
4 #include <framework/logging/Logger.h>
5 #include <framework/utilities/Stream.h>
6 
7 #include <libxml/parser.h>
8 #include <libxml/xinclude.h>
9 #include <libxml/xmlIO.h>
10 #include <cstring>
11 #include <boost/algorithm/string.hpp>
12 
13 #include <TObject.h>
14 
15 using namespace std;
16 
17 namespace Belle2 {
23  namespace gearbox {
25  static int matchXmlUri(const char* uri)
26  {
27  //Ignore uris including file protocol. They are only used if loading of a
28  //resource failed, then libxml will try to look in /etc/xml/catalog if it
29  //can find the file there.
30  if (boost::starts_with(uri, "file:/")) return 0;
31  return 1;
32  }
33 
35  void* openXmlUri(const char* uri)
36  {
37  B2DEBUG(200, "Request to open " << uri);
38  InputContext* context = Gearbox::getInstance().openXmlUri(uri);
39  return (void*) context;
40  }
41 
43  static int readXmlData(void* context, char* buffer, int buffsize)
44  {
45  //B2DEBUG(200,"Calling read to get " << buffsize << " bytes");
46  auto* gearContext = static_cast<InputContext*>(context);
47  return gearContext->readXmlData(buffer, buffsize);
48  }
49 
51  static int closeXmlContext(void* context)
52  {
53  B2DEBUG(200, "Closing context");
54  auto* gearContext = static_cast<InputContext*>(context);
55  delete gearContext;
56  return 0;
57  }
58  }
59 
60  Gearbox::Gearbox(): m_xmlDocument(nullptr), m_xpathContext(nullptr),
61  m_parameterCache(new MRUCache<std::string, PathValue>(c_DefaultCacheSize))
62  {
63  xmlInitParser();
64  LIBXML_TEST_VERSION;
65  }
66 
68  {
69  close();
70  clearBackends();
71  delete m_parameterCache;
72  }
73 
75  {
76  static Gearbox instance;
77  return instance;
78  }
79 
80  gearbox::InputContext* Gearbox::openXmlUri(const string& uri) const
81  {
82  //Check input handlers one by one
83  for (gearbox::InputHandler* handler : m_handlers) {
84  //try to create context for uri, return if success
85  gearbox::InputContext* context = handler->open(uri);
86  if (context) return context;
87  }
88  B2ERROR("Could not find data for uri '" << uri << "'");
89  return nullptr;
90  }
91 
92  void Gearbox::setBackends(const vector<string>& backends)
93  {
94  clearBackends();
95  for (const string& backend : backends) {
96  B2DEBUG(300, "Adding InputHandler for '" << backend << "'");
97  //Find correct InputHandler, assuming file backend by default if there is no colon in the
98  //uri
99  string prefix("file");
100  string accessinfo(backend);
101  size_t colon = backend.find(':');
102  if (colon != string::npos) {
103  prefix = backend.substr(0, colon);
104  accessinfo = backend.substr(colon + 1);
105  }
106  auto it = m_registeredHandlers.find(prefix);
107  if (it == m_registeredHandlers.end()) {
108  B2ERROR("Could not find input handler to handle '" << backend << "', ignoring");
109  continue;
110  }
111  gearbox::InputHandler* handler = it->second(accessinfo);
112  if (handler) {
113  m_handlers.push_back(handler);
114  } else {
115  B2ERROR("Problem creating input handler to handle '" << backend << "', ignoring");
116  }
117  }
118  }
119 
121  {
122  for (gearbox::InputHandler* handler : m_handlers) delete handler;
123  m_handlers.clear();
124  }
125 
126  void Gearbox::open(const std::string& name, size_t cacheSize)
127  {
128  //Check if we have an open connection and close first if so
129  if (m_xmlDocument) close();
130  //Check if we have at least one backend
131  if (m_handlers.empty())
132  B2FATAL("No backends defined, please use Gearbox::setBackends() first to specify how to access XML files.");
133 
134  // register input callbacks for opening the files
135  xmlRegisterInputCallbacks(gearbox::matchXmlUri, gearbox::openXmlUri,
136  gearbox::readXmlData, gearbox::closeXmlContext);
137 
138  //Open document
139  m_xmlDocument = xmlParseFile(name.c_str());
140  //libxml >= 2.7.0 introduced some limits on node size etc. which breaks reading VXDTF files
141  xmlXIncludeProcessFlags(m_xmlDocument, XML_PARSE_HUGE);
142 
143  // reset input callbacks
144  xmlPopInputCallbacks();
145 
146  if (!m_xmlDocument) B2FATAL("Could not connect gearbox to " << name);
147  m_xpathContext = xmlXPathNewContext(m_xmlDocument);
148  if (!m_xpathContext) B2FATAL("Could not create XPath context");
149 
150  //Apply overrides
151  for (const auto& poverride : m_overrides) {
152  overridePathValue(poverride);
153  }
154 
155  //Speeds up XPath computation on static documents.
156  xmlXPathOrderDocElems(m_xmlDocument);
157 
158  //Set cachesize
159  m_parameterCache->setMaxSize(cacheSize);
160  }
161 
163  {
164  if (m_xpathContext) xmlXPathFreeContext(m_xpathContext);
165  if (m_xmlDocument) xmlFreeDoc(m_xmlDocument);
166  m_xpathContext = nullptr;
167  m_xmlDocument = nullptr;
168 
169  for (auto& entry : m_ownedObjects) {
170  delete entry.second;
171  }
172  m_ownedObjects.clear();
173 
174  m_parameterCache->clear();
175  }
176 
178  {
179  if (m_xpathContext == nullptr) B2FATAL("Gearbox is not connected");
180  //Make sure it ends with a slash
181  string query = ensureNode(poverride.path);
182  //Ok, lets search for the path
183  B2INFO("Override '" << poverride.path << "' with '" << poverride.value
184  << "' (unit: '" << poverride.unit << "')");
185  xmlXPathObjectPtr result = xmlXPathEvalExpression((xmlChar*) query.c_str(), m_xpathContext);
186  if (result != nullptr && result->type == XPATH_NODESET && !xmlXPathNodeSetIsEmpty(result->nodesetval)) {
187  //Found it, so let's replace the content
188  int numNodes = xmlXPathNodeSetGetLength(result->nodesetval);
189  if (!poverride.multiple && numNodes > 1) {
190  B2ERROR("Cannot override '" << poverride.path << "': more than one node found");
191  return;
192  } else {
193  B2DEBUG(200, "Found " << numNodes << " nodes, overriding them all");
194  }
195  for (int i = numNodes - 1; i >= 0; --i) {
196  xmlNodePtr node = result->nodesetval->nodeTab[i];
197  //Check if children are only TEXT nodes
198  bool textOnly(true);
199  for (xmlNodePtr child = node->children; child; child = child->next) {
200  textOnly &= child->type == XML_TEXT_NODE;
201  if (!textOnly) break;
202  }
203  if (!textOnly) {
204  B2ERROR("Cannot override '" << poverride.path << "': not just text content");
205  continue;
206  }
207  xmlNodeSetContent(node, BAD_CAST poverride.value.c_str());
208 
209  //Is the path an element? if so replace the unit, otherwise warn
210  if (node->type != XML_ELEMENT_NODE) {
211  if (!poverride.unit.empty())
212  B2WARNING("Cannot set unit '" << poverride.unit << "' on '"
213  << poverride.path << "': not an element");
214  } else {
215  xmlSetProp(node, BAD_CAST "unit", BAD_CAST poverride.unit.c_str());
216  }
217  //From libxml example xpath2.c:
218  //All the elements returned by an XPath query are pointers to
219  //elements from the tree *except* namespace nodes where the XPath
220  //semantic is different from the implementation in libxml2 tree.
221  //As a result when a returned node set is freed when
222  //xmlXPathFreeObject() is called, that routine must check the
223  //element type. But node from the returned set may have been removed
224  //by xmlNodeSetContent() resulting in access to freed data.
225  //There is 2 ways around it:
226  // - make a copy of the pointers to the nodes from the result set
227  // then call xmlXPathFreeObject() and then modify the nodes
228  //or
229  // - remove the reference to the modified nodes from the node set
230  // as they are processed, if they are not namespace nodes.
231  if (node->type != XML_NAMESPACE_DECL)
232  result->nodesetval->nodeTab[i] = nullptr;
233  }
234  } else {
235  B2ERROR("Cannot override '" << poverride.path << "': not found");
236  }
237  xmlXPathFreeObject(result);
238  }
239 
240  Gearbox::PathValue Gearbox::getPathValue(const std::string& path) const
241  {
242  PathValue value;
243  if (m_xpathContext == nullptr) B2FATAL("Gearbox is not connected");
244  //Get from cache if possible
245  if (m_parameterCache->retrieve(path, value)) {
246  return value;
247  }
248  //Nothing in cache, query xml
249  string query = ensureNode(path);
250  B2DEBUG(1000, "Gearbox XPath query: " << query);
251  xmlXPathObjectPtr result = xmlXPathEvalExpression((xmlChar*) query.c_str(), m_xpathContext);
252  if (result != nullptr && result->type == XPATH_NODESET && !xmlXPathNodeSetIsEmpty(result->nodesetval)) {
253  value.numNodes = xmlXPathNodeSetGetLength(result->nodesetval);
254  xmlNodePtr node = result->nodesetval->nodeTab[0];
255  //Example: <foo><bar/></foo>
256  // - bar has no children, so node->children is 0
257  // - foo has not text children, so node->children->content should be 0
258  // but xmlXPathOrderDocElems assigns them an index<0 to speed up XPath
259  // so we have to cast to a long integer and check if it is positive
260  if (node->children && (long)node->children->content > 0) {
261  xmlChar* valueString = xmlNodeListGetString(m_xmlDocument, node->children, 1);
262  value.value = (char*)valueString;
263  xmlFree(valueString);
264  }
265  //See if we have a unit attribute and add it
266  xmlAttrPtr attribute = node->properties;
267  while (attribute) {
268  B2DEBUG(1001, "Checking attribute " << attribute->name);
269  if (!strcmp((char*)attribute->name, "unit")) {
270  B2DEBUG(1001, "found Unit " << attribute->children->content);
271  value.unit = (char*)attribute->children->content;
272  break;
273  }
274  attribute = attribute->next;
275  }
276  //Remove leading and trailing whitespaces
277  boost::trim(value.value);
278  boost::trim(value.unit);
279  }
280  //Add to cache, empty or not: results won't change
281  m_parameterCache->insert(path, value);
282  B2DEBUG(1000, "Gearbox XPath result: " << value.numNodes << ", " << value.value << ", " << value.unit);
283 
284  xmlXPathFreeObject(result);
285  return value;
286  }
287 
288  const TObject* Gearbox::getTObject(const std::string& path) const noexcept(false)
289  {
290  //do we already have an object for this path?
291  auto it = m_ownedObjects.find(path);
292  if (it != m_ownedObjects.end())
293  return it->second;
294 
295  const string& value = getString(path);
296  //assume base64-encoded raw data.
297  TObject* object = Stream::deserializeEncodedRawData(value);
298  if (!object)
299  throw gearbox::TObjectConversionError() << path;
300 
301  m_ownedObjects[path] = object;
302 
303  return object;
304  }
305 
306 
307  GearDir Gearbox::getDetectorComponent(const string& component)
308  {
309  return GearDir("/Detector/DetectorComponent[@name='" + component + "']/Content");
310  }
312 }
Belle2::Gearbox::getDetectorComponent
GearDir getDetectorComponent(const std::string &component)
Return GearDir representing a given DetectorComponent.
Definition: Gearbox.cc:307
Belle2::Gearbox::m_handlers
std::vector< gearbox::InputHandler * > m_handlers
List of input handlers which will be used to find resources.
Definition: Gearbox.h:228
Belle2::Gearbox::PathOverride::value
std::string value
New value.
Definition: Gearbox.h:65
Belle2::Gearbox::getTObject
virtual const TObject * getTObject(const std::string &path) const noexcept(false) override
Get the parameter path as a TObject.
Definition: Gearbox.cc:288
Belle2::Gearbox::getInstance
static Gearbox & getInstance()
Return reference to the Gearbox instance.
Definition: Gearbox.cc:74
Belle2::Gearbox::clearBackends
void clearBackends()
Clear list of backends.
Definition: Gearbox.cc:120
Belle2::Gearbox::m_overrides
std::vector< PathOverride > m_overrides
the existing overrides
Definition: Gearbox.h:233
Belle2::Gearbox::m_registeredHandlers
std::map< std::string, gearbox::InputHandler::Factory * > m_registeredHandlers
Map of registered InputHandlers.
Definition: Gearbox.h:230
Belle2::Gearbox::PathOverride::multiple
bool multiple
if true, override all nodes when more than one node matches the XPath expression, bail otherwise
Definition: Gearbox.h:70
Belle2::MRUCache
Class implementing a generic Most Recently Used cache.
Definition: MRUCache.h:58
Belle2::Gearbox::openXmlUri
friend void * gearbox::openXmlUri(const char *)
friend to internal c-like function to interface libxml2 callback
Belle2::Gearbox::PathOverride
Struct to override a path in the XML file with a custom value.
Definition: Gearbox.h:61
Belle2::Gearbox::PathValue
Struct for caching results from the xml file.
Definition: Gearbox.h:50
Belle2::Gearbox::close
void close()
Free internal structures of previously parsed tree and clear cache.
Definition: Gearbox.cc:162
Belle2
Abstract base class for different kinds of events.
Definition: MillepedeAlgorithm.h:19
Belle2::Gearbox
Singleton class responsible for loading detector parameters from an XML file.
Definition: Gearbox.h:44
Belle2::GearDir
GearDir is the basic class used for accessing the parameter store.
Definition: GearDir.h:41
Belle2::Gearbox::m_xmlDocument
xmlDocPtr m_xmlDocument
Pointer to the libxml Document structure.
Definition: Gearbox.h:219
Belle2::Gearbox::m_ownedObjects
std::map< std::string, TObject * > m_ownedObjects
Map of queried objects (path -> TObject*).
Definition: Gearbox.h:225
Belle2::gearbox::Interface::ensureNode
std::string ensureNode(const std::string &path) const
make sure the path really corresponds to an XPath node expression by removing trailing slashes
Definition: Interface.cc:166
Belle2::Gearbox::PathOverride::path
std::string path
XPath expression of the path to override.
Definition: Gearbox.h:63
Belle2::Gearbox::m_xpathContext
xmlXPathContextPtr m_xpathContext
Pointer to the libxml XPath context.
Definition: Gearbox.h:221
Belle2::Gearbox::m_parameterCache
MRUCache< std::string, PathValue > * m_parameterCache
Cache for already queried paths.
Definition: Gearbox.h:223
Belle2::Gearbox::~Gearbox
~Gearbox()
Free structures on destruction.
Definition: Gearbox.cc:67
Belle2::Gearbox::getPathValue
PathValue getPathValue(const std::string &path) const
Return the (cached) value of a given path.
Definition: Gearbox.cc:240
Belle2::Gearbox::open
void open(const std::string &name="Belle2.xml", size_t cacheSize=c_DefaultCacheSize)
Open connection to backend and parse tree.
Definition: Gearbox.cc:126
Belle2::gearbox::InputContext
Class representing a resource context for gearbox.
Definition: InputHandler.h:35
Belle2::gearbox::InputHandler
Class to provide an InputContext for a given XML resource name.
Definition: InputHandler.h:57
Belle2::Gearbox::openXmlUri
gearbox::InputContext * openXmlUri(const std::string &uri) const
Function to be called when libxml requests a new input uri to be opened.
Definition: Gearbox.cc:80
Belle2::Gearbox::overridePathValue
void overridePathValue(const PathOverride &poverride)
Change the value of a given path expression.
Definition: Gearbox.cc:177
Belle2::Gearbox::PathOverride::unit
std::string unit
new Unit
Definition: Gearbox.h:67
Belle2::Stream::deserializeEncodedRawData
TObject * deserializeEncodedRawData(const std::string &base64Data)
Convert given serialized raw data back into TObject.
Definition: Stream.cc:65
Belle2::Gearbox::setBackends
void setBackends(const std::vector< std::string > &backends)
Select the backends to use to find resources.
Definition: Gearbox.cc:92