fw4spl
BundleDescriptorReader.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2017.
3  * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
4  * published by the Free Software Foundation.
5  * ****** END LICENSE BLOCK ****** */
6 
7 #include "fwRuntime/io/BundleDescriptorReader.hpp"
8 
9 #include "fwRuntime/Bundle.hpp"
10 #include "fwRuntime/ConfigurationElement.hpp"
11 #include "fwRuntime/dl/Library.hpp"
12 #include "fwRuntime/Extension.hpp"
13 #include "fwRuntime/ExtensionPoint.hpp"
14 #include "fwRuntime/io/Validator.hpp"
15 #include "fwRuntime/operations.hpp"
16 
17 #include <boost/filesystem/operations.hpp>
18 #include <boost/filesystem/path.hpp>
19 
20 #include <libxml/parser.h>
21 #include <libxml/xinclude.h>
22 
23 #include <iostream>
24 #include <sstream>
25 #include <string>
26 namespace fwRuntime
27 {
28 
29 namespace io
30 {
31 
32 std::string BundleDescriptorReader::CLASS("class");
33 std::string BundleDescriptorReader::EXTENSION("extension");
34 std::string BundleDescriptorReader::EXTENSION_POINT("extension-point");
35 std::string BundleDescriptorReader::ID("id");
36 std::string BundleDescriptorReader::IMPLEMENTS("implements");
37 std::string BundleDescriptorReader::LIBRARY("library");
38 std::string BundleDescriptorReader::NAME("name");
39 std::string BundleDescriptorReader::PLUGIN("plugin");
40 std::string BundleDescriptorReader::REQUIREMENT("requirement");
41 std::string BundleDescriptorReader::SCHEMA("schema");
42 std::string BundleDescriptorReader::VERSION("version");
43 std::string BundleDescriptorReader::POINT("point");
44 
45 //------------------------------------------------------------------------------
46 
47 const BundleDescriptorReader::BundleContainer BundleDescriptorReader::createBundles(
48  const ::boost::filesystem::path& location)
49 {
50  // Normalizes the path.
51  ::boost::filesystem::path normalizedPath(location);
52  normalizedPath.normalize();
53 
54  // Asserts that the repository is a valid directory path.
55  if(::boost::filesystem::exists(normalizedPath) == false ||
56  ::boost::filesystem::is_directory(normalizedPath) == false)
57  {
58  throw RuntimeException("'" + normalizedPath.string() + "': not a directory.");
59  }
60 
61  // Walk through the repository entries.
62  BundleContainer bundles;
63  ::boost::filesystem::directory_iterator currentEntry(normalizedPath);
64  ::boost::filesystem::directory_iterator endEntry;
65  for(; currentEntry != endEntry; ++currentEntry)
66  {
67  ::boost::filesystem::path entryPath = *currentEntry;
68 
69  if(::boost::filesystem::is_directory(entryPath))
70  {
71  try
72  {
74  if(bundle)
75  {
76  bundles.push_back( bundle );
77  }
78  }
79  catch(const RuntimeException& runtimeException)
80  {
81  OSLM_INFO( "'"<< entryPath.string() << "': skipped. " << runtimeException.what() );
82  }
83  catch(const ::fwCore::Exception& exception)
84  {
85  OSLM_INFO( "'"<< entryPath.string() << "': skipped. " << exception.what() );
86  }
87  }
88  }
89  return bundles;
90 }
91 
92 //------------------------------------------------------------------------------
93 
94 std::shared_ptr<Bundle> BundleDescriptorReader::createBundle(const ::boost::filesystem::path& location)
95 {
96  std::shared_ptr<Bundle> bundle;
97 
98  ::boost::filesystem::path descriptorLocation(location / "plugin.xml");
99  if(::boost::filesystem::exists(descriptorLocation) == false)
100  {
101  throw ::fwCore::Exception(std::string("'plugin.xml': file not found in ") + location.string());
102  }
103 
104  // Validation
105  auto pluginXSDLocation = ::fwRuntime::getLibraryResourceFilePath("fwRuntime-" FWRUNTIME_VER "/plugin.xsd");
106 
107  Validator validator(pluginXSDLocation);
108  if( validator.validate(descriptorLocation) == false )
109  {
110  throw RuntimeException("Invalid bundle descriptor file. " + validator.getErrorLog());
111  }
112 
113  // Get the document.
114  xmlDocPtr document = xmlParseFile( descriptorLocation.string().c_str() );
115  if(document == 0)
116  {
117  throw RuntimeException("Unable to read the bundle descriptor file.");
118  }
119 
120  try
121  {
122  // Get the root node.
123  xmlNodePtr rootNode = xmlDocGetRootElement(document);
124 
125  if (xmlXIncludeProcessTreeFlags(rootNode, XML_PARSE_NOBASEFIX) == -1)
126  {
127  throw RuntimeException("Unable to manage xinclude !");
128  }
129 
130  if(xmlStrcmp(rootNode->name, (const xmlChar*) PLUGIN.c_str()) != 0)
131  {
132  throw RuntimeException("Unexpected XML element");
133  }
134 
135  // Creates and process the plugin element.
136  // Get the descriptor location.
137  ::boost::filesystem::path completeLocation(location);
138 
139  if(!completeLocation.is_complete())
140  {
141  completeLocation = ::fwRuntime::Runtime::getDefault()->getWorkingPath() / location;
142  }
143 
144  bundle = processPlugin(rootNode, completeLocation);
145 
146  // Job's done!
147  xmlFreeDoc(document);
148  }
149  catch(std::exception& exception)
150  {
151  xmlFreeDoc(document);
152  throw;
153  }
154  return bundle;
155 }
156 
157 //-----------------------------------------------------------------------------
158 
159 ConfigurationElement::sptr BundleDescriptorReader::processConfigurationElement(xmlNodePtr node,
160  const std::shared_ptr<Bundle> bundle)
161 
162 {
163  //xmlKeepBlanksDefault(0);
164  // Creates the configuration element.
165  std::string name((const char*) node->name);
166  ConfigurationElement::sptr configurationElement(new ConfigurationElement(bundle, name));
167 
168  // Processes all attributes.
169  xmlAttrPtr curAttr;
170  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
171  {
172  std::string name((const char*) curAttr->name);
173  std::string value((const char*) curAttr->children->content);
174 
175  configurationElement->setAttributeValue(name, value);
176  }
177 
178  // Process child nodes.
179  xmlNodePtr curChild = node->children;
180  for(curChild = node->children; curChild != 0; curChild = curChild->next)
181  {
182  if(curChild->type == XML_TEXT_NODE && !xmlIsBlankNode(curChild))
183  {
184  std::string value((const char*) curChild->content);
185  // Even whitespace (non XML_TEXT_NODE) are considered as valid XML_TEXT_NODE
186  OSLM_WARN_IF(
187  "Bundle : " << ( bundle ? bundle->getIdentifier() : "<None>" ) << ", node: " << name << ", blanks in xml nodes can result in unexpected behaviour. Consider using <![CDATA[ ... ]]>.",
188  (value.find("\n") != std::string::npos || value.find("\t") != std::string::npos));
189 
190  configurationElement->setValue( configurationElement->getValue() + value );
191  continue;
192  }
193  else if(curChild->type == XML_CDATA_SECTION_NODE )
194  {
195  std::string value((const char*) curChild->content);
196  configurationElement->setValue( configurationElement->getValue() + value );
197  continue;
198  }
199 
200  else if(curChild->type == XML_ELEMENT_NODE)
201  {
202  ConfigurationElement::sptr element(processConfigurationElement(curChild, bundle));
203  configurationElement->addConfigurationElement(element);
204  continue;
205  }
206  }
207 
208  // Job's done.
209  return configurationElement;
210 }
211 
212 //------------------------------------------------------------------------------
213 
214 std::shared_ptr<Extension> BundleDescriptorReader::processExtension(xmlNodePtr node,
215  const std::shared_ptr<Bundle> bundle)
216 {
217  // Processes all extension attributes.
218  xmlAttrPtr curAttr;
219  std::string point;
220  std::string identifier;
221  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
222  {
223  if(xmlStrcmp(curAttr->name, (const xmlChar*) ID.c_str()) == 0)
224  {
225  identifier = (const char*) curAttr->children->content;
226  continue;
227  }
228 
229  if(xmlStrcmp(curAttr->name, (const xmlChar*) IMPLEMENTS.c_str()) == 0)
230  {
231  point = (const char*) curAttr->children->content;
232  continue;
233  }
234  }
235 
236  // Creates the extension instance.
237  std::shared_ptr<Extension> extension(new Extension(bundle, identifier, point, node));
238 
239  // Processes child nodes which are configuration elements.
240  xmlNodePtr curChild;
241  for(curChild = node->children; curChild != 0; curChild = curChild->next)
242  {
243  if(curChild->type == XML_ELEMENT_NODE)
244  {
245  ConfigurationElement::sptr element(processConfigurationElement(curChild, bundle));
246  extension->addConfigurationElement(element);
247  }
248  }
249 
250  // Job's done.
251  return extension;
252 }
253 
254 //------------------------------------------------------------------------------
255 
256 BundleDescriptorReader::PointExtensionsPairType BundleDescriptorReader::processPoint(xmlNodePtr node,
257  const std::shared_ptr<Bundle> bundle)
258 {
259  // Creates the extension instance.
260  xmlAttrPtr curAttr;
261  std::string schema;
262  std::string identifier;
263  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
264  {
265  if(xmlStrcmp(curAttr->name, (const xmlChar*) ID.c_str()) == 0)
266  {
267  identifier = (const char*) curAttr->children->content;
268  continue;
269  }
270 
271  if(xmlStrcmp(curAttr->name, (const xmlChar*) SCHEMA.c_str()) == 0)
272  {
273  schema = (const char*) curAttr->children->content;
274  continue;
275  }
276  }
277  std::shared_ptr<ExtensionPoint> extensionPoint(new ExtensionPoint(bundle, identifier, schema));
278 
279  // Processes child nodes which declare identifier as extensions.
280  std::vector< std::shared_ptr<Extension> > extensionContainer;
281  xmlNodePtr curChild;
282  for(curChild = node->children; curChild != 0; curChild = curChild->next)
283  {
284  if(curChild->type == XML_ELEMENT_NODE)
285  {
286  if( xmlStrcmp(curChild->name, (const xmlChar*) IMPLEMENTS.c_str()) == 0 )
287  {
288  std::string extensionId = (const char*) curChild->children->content;
289  std::shared_ptr<Extension> extension(new Extension(bundle, identifier, extensionId, curChild));
290  extensionContainer.push_back( extension );
291  }
292  }
293  }
294 
295  return PointExtensionsPairType( extensionPoint, extensionContainer );
296 }
297 
298 //------------------------------------------------------------------------------
299 
300 std::shared_ptr<ExtensionPoint> BundleDescriptorReader::processExtensionPoint(xmlNodePtr node,
301  const std::shared_ptr<Bundle> bundle)
302 
303 {
304  // Processes all extension attributes.
305  xmlAttrPtr curAttr;
306  std::string identifier;
307  std::string schema;
308  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
309  {
310  if(xmlStrcmp(curAttr->name, (const xmlChar*) ID.c_str()) == 0)
311  {
312  identifier = (const char*) curAttr->children->content;
313  continue;
314  }
315 
316  if(xmlStrcmp(curAttr->name, (const xmlChar*) SCHEMA.c_str()) == 0)
317  {
318  schema = (const char*) curAttr->children->content;
319  continue;
320  }
321  }
322  // Creates the extension instance.
323  std::shared_ptr<ExtensionPoint> point(new ExtensionPoint(bundle, identifier, schema));
324 
325  // Job's done.
326  return point;
327 }
328 
329 //------------------------------------------------------------------------------
330 
331 std::shared_ptr<dl::Library> BundleDescriptorReader::processLibrary(xmlNodePtr node)
332 {
333  // Processes all plugin attributes.
334  xmlAttrPtr curAttr;
335  std::string name;
336  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
337  {
338  if(xmlStrcmp(curAttr->name, (const xmlChar*) NAME.c_str()) == 0)
339  {
340  name = (const char*) curAttr->children->content;
341  continue;
342  }
343  }
344 
345  // Creates the library
346  std::shared_ptr<dl::Library> library( new dl::Library(name) );
347  return library;
348 }
349 
350 //------------------------------------------------------------------------------
351 
352 std::shared_ptr<Bundle> BundleDescriptorReader::processPlugin(xmlNodePtr node,
353  const ::boost::filesystem::path& location)
354 {
355  // Creates the bundle.
356  std::shared_ptr<Bundle> bundle;
357  // Processes all plugin attributes.
358  xmlAttrPtr curAttr;
359  std::string bundleIdentifier;
360  std::string version;
361  std::string pluginClass;
362  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
363  {
364  if(xmlStrcmp(curAttr->name, (const xmlChar*) ID.c_str()) == 0)
365  {
366  bundleIdentifier = (const char*) curAttr->children->content;
367  continue;
368  }
369 
370  if(xmlStrcmp(curAttr->name, (const xmlChar*) CLASS.c_str()) == 0)
371  {
372  pluginClass = (const char*) curAttr->children->content;
373  continue;
374  }
375 
376  if(xmlStrcmp(curAttr->name, (const xmlChar*) VERSION.c_str()) == 0)
377  {
378  version = (const char*) curAttr->children->content;
379  continue;
380  }
381  }
382  SLM_ASSERT("bundle identifier is empty", !bundleIdentifier.empty());
383 
384  if( ::fwRuntime::Runtime::getDefault()->findBundle(bundleIdentifier, Version(version)))
385  {
386  return bundle;
387  }
388  if(pluginClass.empty() == true)
389  {
390  bundle = std::shared_ptr<Bundle>( new Bundle(location, bundleIdentifier, version) );
391  }
392  else
393  {
394  bundle = std::shared_ptr<Bundle>( new Bundle(location, bundleIdentifier, version, pluginClass) );
395  }
396 
397  // Processes all child nodes.
398  xmlNodePtr curChild;
399  for(curChild = node->children; curChild != 0; curChild = curChild->next)
400  {
401  // Skip non element nodes.
402  if(curChild->type != XML_ELEMENT_NODE)
403  {
404  continue;
405  }
406 
407  // Extension declaration.
408  if(xmlStrcmp(curChild->name, (const xmlChar*) EXTENSION.c_str()) == 0)
409  {
410  std::shared_ptr<Extension> extension(processExtension(curChild, bundle));
411  bundle->addExtension(extension);
412  continue;
413  }
414 
415  // Extension point declaration.
416  if(xmlStrcmp(curChild->name, (const xmlChar*) EXTENSION_POINT.c_str()) == 0)
417  {
418  std::shared_ptr<ExtensionPoint> point(processExtensionPoint(curChild, bundle));
419  bundle->addExtensionPoint(point);
420  continue;
421  }
422 
423  // Library declaration.
424  if(xmlStrcmp(curChild->name, (const xmlChar*) LIBRARY.c_str()) == 0)
425  {
426  std::shared_ptr<dl::Library> library(processLibrary(curChild));
427  bundle->addLibrary(library);
428  continue;
429  }
430 
431  // Requirement declaration.
432  if(xmlStrcmp(curChild->name, (const xmlChar*) REQUIREMENT.c_str()) == 0)
433  {
434  const std::string requirement(processRequirement(curChild));
435  bundle->addRequirement(requirement);
436  }
437 
438  // Point declaration.
439  if(xmlStrcmp(curChild->name, (const xmlChar*) POINT.c_str()) == 0)
440  {
441  SLM_FATAL("This xml element ( <point ... > </point> ) is deprecated (" + location.string() + ")" );
442  }
443  }
444 
445  // Job's done.
446  return bundle;
447 }
448 
449 //------------------------------------------------------------------------------
450 
451 const std::string BundleDescriptorReader::processRequirement(xmlNodePtr node)
452 {
453  // Processes all requirement attributes.
454  xmlAttrPtr curAttr;
455  std::string identifier;
456  for(curAttr = node->properties; curAttr != 0; curAttr = curAttr->next)
457  {
458  if(xmlStrcmp(curAttr->name, (const xmlChar*) ID.c_str()) == 0)
459  {
460  identifier = (const char*) curAttr->children->content;
461  continue;
462  }
463  }
464 
465  // Do some sanity checking.
466  if(identifier.length() == 0)
467  {
468  throw RuntimeException("Invalid attribute.");
469  }
470 
471  // Job's done
472  return identifier;
473 }
474 
475 //------------------------------------------------------------------------------
476 
477 } // namesapce io
478 } // namespace fwRuntime
static FWRUNTIME_API Runtime * getDefault()
Retrieves the default runtime instance.
Definition: Runtime.cpp:286
#define SPTR(_cls_)
static FWRUNTIME_API const BundleContainer createBundles(const boost::filesystem::path &location)
Creates all bundles that are found at the given location.
Defines the extension class.
Definition: Extension.hpp:30
Defines the runtime exception class.
FWRUNTIME_API const std::string getErrorLog() const
Retrieves the error log content.
Definition: Validator.cpp:73
FWRUNTIME_API bool validate(const boost::filesystem::path &xmlFile)
Validates the given file.
Definition: Validator.cpp:120
static FWRUNTIME_API std::shared_ptr< ConfigurationElement > processConfigurationElement(xmlNodePtr node, const std::shared_ptr< Bundle > bundle)
Processes a configuration element XML node.
Defines the extension point class.
#define OSLM_INFO(message)
Definition: spyLog.hpp:252
Defines the configuration element class.
FWRUNTIME_API std::shared_ptr< Bundle > findBundle(const std::string &identifier, const Version &version=Version()) const
Retrieves the bundle for the specified idenfier.
Definition: Runtime.cpp:254
static FWRUNTIME_API std::shared_ptr< Bundle > createBundle(const boost::filesystem::path &location)
Look for a descriptor at the specified location, reads it and creates a bundle with it...
The namespace fwRuntime contains classes to manage bundle, configuration element, extension point in ...
#define SLM_FATAL(message)
Definition: spyLog.hpp:283
Implements an XML validator.
Definition: Validator.hpp:37
Defines the module class.This class is only a bridge to a native module implementor.
Definition: Library.hpp:30
#define SLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:308
std::pair< std::shared_ptr< ExtensionPoint >, std::vector< std::shared_ptr< Extension > > > PointExtensionsPairType
Pair of created extension point associated with extensions.
Defines the bundle class.
Definition: Bundle.hpp:45
Holds version information for libraries and bundles.
#define OSLM_WARN_IF(message, cond)
Definition: spyLog.hpp:267
FWRUNTIME_API::boost::filesystem::path getWorkingPath() const
Get the path where Bundles and share folder are located.
Definition: Runtime.cpp:402