001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.plugin; 017 018import java.io.BufferedReader; 019import java.io.File; 020import java.io.FileFilter; 021import java.io.FileInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.LinkedHashMap; 033import java.util.Map; 034import java.util.Map.Entry; 035import java.util.Set; 036 037import javax.xml.XMLConstants; 038import javax.xml.parsers.SAXParserFactory; 039import javax.xml.validation.Schema; 040import javax.xml.validation.SchemaFactory; 041 042import org.apache.avalon.framework.component.ComponentManager; 043import org.apache.avalon.framework.configuration.Configuration; 044import org.apache.avalon.framework.configuration.DefaultConfiguration; 045import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 046import org.apache.avalon.framework.context.Context; 047import org.apache.avalon.framework.service.WrapperServiceManager; 048import org.apache.commons.collections.CollectionUtils; 049import org.apache.commons.io.IOUtils; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052import org.xml.sax.XMLReader; 053 054import org.ametys.runtime.config.ConfigManager; 055import org.ametys.runtime.plugin.PluginIssue.PluginIssueCode; 056import org.ametys.runtime.plugin.component.PluginsComponentManager; 057import org.ametys.runtime.servlet.RuntimeConfig; 058 059/** 060 * The PluginManager is in charge to load and initialize plugins. <br> 061 * It gives access to the extension points. 062 */ 063public final class PluginsManager 064{ 065 /** The regexp to determine if a plugin name is ignored (CVS or .* or *.bak or *.old)*/ 066 public static final String PLUGIN_NAMES_IGNORED = "^CVS|\\..+|.*\\.bak|.*\\.old$"; 067 068 /** The regexp to determine if a plugin name is correct (add ^ and $ as delimiters if this is your only test) */ 069 public static final String PLUGIN_NAME_REGEXP = "[a-zA-Z0-9](?:[a-zA-Z0-9-_\\.]*[a-zA-Z0-9])?"; 070 071 /** Separator between pluginName and featureName */ 072 public static final String FEATURE_ID_SEPARATOR = "/"; 073 074 /** Plugin filename */ 075 public static final String PLUGIN_FILENAME = "plugin.xml"; 076 077 // shared instance 078 private static PluginsManager __instance; 079 080 // safe mode flag 081 private boolean _safeMode; 082 083 // associations plugins/resourcesURI 084 private Map<String, String> _resourceURIs; 085 086 // plugins/locations association 087 private Map<String, File> _locations; 088 089 // All readable plugins 090 private Map<String, Plugin> _allPlugins; 091 092 // Active plugins 093 private Map<String, Plugin> _plugins; 094 095 // Loaded features 096 private Map<String, Feature> _features; 097 098 // All declared features 099 private Map<String, InactivityCause> _inactiveFeatures; 100 101 // All declared extension points 102 private Map<String, ExtensionPointDefinition> _extensionPoints; 103 104 // Declared components, stored by role 105 private Map<String, ComponentDefinition> _components; 106 107 // Declared extension, grouped by extension point 108 private Map<String, Map<String, ExtensionDefinition>> _extensions; 109 110 // status after initialization 111 private Status _status; 112 113 // Logger for traces 114 private Logger _logger = LoggerFactory.getLogger(PluginsManager.class); 115 116 // Errors collected during system initialization 117 private Collection<PluginIssue> _errors = new ArrayList<>(); 118 119 private PluginsManager() 120 { 121 // empty constructor 122 } 123 124 /** 125 * Returns the shared instance of the <code>PluginManager</code> 126 * @return the shared instance of the PluginManager 127 */ 128 public static PluginsManager getInstance() 129 { 130 if (__instance == null) 131 { 132 __instance = new PluginsManager(); 133 } 134 135 return __instance; 136 } 137 138 /** 139 * Returns true if the safe mode is activated. 140 * @return true if the safe mode is activated. 141 */ 142 public boolean isSafeMode() 143 { 144 return _safeMode; 145 } 146 147 /** 148 * Returns errors gathered during plugins loading. 149 * @return errors gathered during plugins loading. 150 */ 151 public Collection<PluginIssue> getErrors() 152 { 153 return _errors; 154 } 155 156 /** 157 * Returns the names of the plugins 158 * @return the names of the plugins 159 */ 160 public Set<String> getPluginNames() 161 { 162 return Collections.unmodifiableSet(_plugins.keySet()); 163 } 164 165 /** 166 * Returns a String array containing the names of the plugins bundled in jars 167 * @return a String array containing the names of the plugins bundled in jars 168 */ 169 public Set<String> getBundledPluginsNames() 170 { 171 return Collections.unmodifiableSet(_resourceURIs.keySet()); 172 } 173 174 /** 175 * Returns active plugins declarations. 176 * @return active plugins declarations. 177 */ 178 public Map<String, Plugin> getPlugins() 179 { 180 return Collections.unmodifiableMap(_plugins); 181 } 182 183 /** 184 * Returns all existing plugins definitions. 185 * @return all existing plugins definitions. 186 */ 187 public Map<String, Plugin> getAllPlugins() 188 { 189 return Collections.unmodifiableMap(_allPlugins); 190 } 191 192 /** 193 * Returns loaded features declarations. <br>They may be different than active feature in case of safe mode. 194 * @return loaded features declarations. 195 */ 196 public Map<String, Feature> getFeatures() 197 { 198 return Collections.unmodifiableMap(_features); 199 } 200 201 /** 202 * Returns inactive features id and cause of deactivation. 203 * @return inactive features id and cause of deactivation. 204 */ 205 public Map<String, InactivityCause> getInactiveFeatures() 206 { 207 return Collections.unmodifiableMap(_inactiveFeatures); 208 } 209 210 /** 211 * Returns the extensions points and their extensions 212 * @return the extensions points and their extensions 213 */ 214 public Map<String, Collection<String>> getExtensionPoints() 215 { 216 Map<String, Collection<String>> result = new HashMap<>(); 217 218 for (String point : _extensions.keySet()) 219 { 220 result.put(point, _extensions.get(point).keySet()); 221 } 222 223 return Collections.unmodifiableMap(result); 224 } 225 226 /** 227 * Returns the components roles. 228 * @return the components roles. 229 */ 230 public Collection<String> getComponents() 231 { 232 return Collections.unmodifiableCollection(_components.keySet()); 233 } 234 235 /** 236 * Returns the base URI for the given plugin resources, or null if the plugin does not exist or is located in the file system. 237 * @param pluginName the name of the plugin 238 * @return the base URI for the given plugin resources, or null if the plugin does not exist or is located in the file system. 239 */ 240 public String getResourceURI(String pluginName) 241 { 242 String pluginUri = _resourceURIs.get(pluginName); 243 if (pluginUri == null || !_plugins.containsKey(pluginName)) 244 { 245 return null; 246 } 247 248 return "resource:/" + pluginUri; 249 } 250 251 /** 252 * Returns the plugin filesystem location for the given plugin or null if the plugin is loaded from the classpath. 253 * @param pluginName the plugin name 254 * @return the plugin location for the given plugin 255 */ 256 public File getPluginLocation(String pluginName) 257 { 258 return _locations.get(pluginName); 259 } 260 261 /** 262 * Returns the status after initialization. 263 * @return the status after initialization. 264 */ 265 public Status getStatus() 266 { 267 return _status; 268 } 269 270 /** 271 * Initialization of the plugin manager 272 * @param parentCM the parent {@link ComponentManager}. 273 * @param context the Avalon context 274 * @param contextPath the Web context path on the server filesystem 275 * @param forceSafeMode true to force the application to enter the safe mode 276 * @return the {@link PluginsComponentManager} containing loaded components. 277 * @throws Exception if something wrong occurs during plugins loading 278 */ 279 public PluginsComponentManager init(ComponentManager parentCM, Context context, String contextPath, boolean forceSafeMode) throws Exception 280 { 281 _resourceURIs = new HashMap<>(); 282 _locations = new HashMap<>(); 283 _errors = new ArrayList<>(); 284 285 _safeMode = false; 286 287 // Bundled plugins locations 288 _initResourceURIs(); 289 290 // Additional plugins 291 Map<String, File> externalPlugins = RuntimeConfig.getInstance().getExternalPlugins(); 292 293 // Check external plugins 294 for (File plugin : externalPlugins.values()) 295 { 296 if (!plugin.exists() || !plugin.isDirectory()) 297 { 298 throw new RuntimeException("The configured external plugin is not an existing directory: " + plugin.getAbsolutePath()); 299 } 300 } 301 302 // Plugins root directories (directories containing plugins directories) 303 Collection<String> locations = RuntimeConfig.getInstance().getPluginsLocations(); 304 305 // List of chosen components 306 Map<String, String> componentsConfig = RuntimeConfig.getInstance().getComponents(); 307 308 // List of manually excluded plugins 309 Collection<String> excludedPlugins = RuntimeConfig.getInstance().getExcludedPlugins(); 310 311 // List of manually excluded features 312 Collection<String> excludedFeatures = RuntimeConfig.getInstance().getExcludedFeatures(); 313 314 // Parse all plugin.xml 315 _allPlugins = _parsePlugins(contextPath, locations, externalPlugins, excludedPlugins); 316 317 if (RuntimeConfig.getInstance().isSafeMode()) 318 { 319 _status = Status.RUNTIME_NOT_LOADED; 320 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 321 return safeManager; 322 } 323 324 // Get active feature list 325 PluginsInformation info = computeActiveFeatures(contextPath, excludedPlugins, excludedFeatures, componentsConfig); 326 327 Map<String, Plugin> plugins = info.getPlugins(); 328 Map<String, Feature> features = info.getFeatures(); 329 _errors.addAll(info.getErrors()); 330 331 // At this point, extension points, active and inactive features are known 332 if (_logger.isDebugEnabled()) 333 { 334 _logger.debug("All declared plugins : \n\n" + dump(info.getInactiveFeatures())); 335 } 336 337 if (!_errors.isEmpty()) 338 { 339 _status = Status.WRONG_DEFINITIONS; 340 PluginsComponentManager manager = _enterSafeMode(parentCM, context, contextPath); 341 return manager; 342 } 343 344 // Create the ComponentManager 345 PluginsComponentManager manager = new PluginsComponentManager(parentCM); 346 manager.setLogger(LoggerFactory.getLogger("org.ametys.runtime.plugin.manager")); 347 manager.contextualize(context); 348 349 // Config loading 350 ConfigManager configManager = ConfigManager.getInstance(); 351 352 configManager.contextualize(context); 353 configManager.service(new WrapperServiceManager(manager)); 354 configManager.initialize(); 355 356 // Global config parameter loading 357 for (String pluginName : plugins.keySet()) 358 { 359 Plugin plugin = plugins.get(pluginName); 360 configManager.addGlobalConfig(pluginName, plugin.getConfigParameters(), plugin.getParameterCheckers()); 361 } 362 363 // "local" config parameter loading 364 for (String featureId : features.keySet()) 365 { 366 Feature feature = features.get(featureId); 367 configManager.addConfig(feature.getFeatureId(), feature.getConfigParameters(), feature.getConfigParametersReferences(), feature.getParameterCheckers()); 368 } 369 370 // check if the config is complete and valid 371 configManager.validate(); 372 373 // force safe mode if requested 374 if (forceSafeMode) 375 { 376 _status = Status.SAFE_MODE_FORCED; 377 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 378 return safeManager; 379 } 380 381 // config file does not exist 382 if (configManager.isEmpty()) 383 { 384 _status = Status.NO_CONFIG; 385 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 386 return safeManager; 387 } 388 389 if (!configManager.isComplete()) 390 { 391 _status = Status.CONFIG_INCOMPLETE; 392 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 393 return safeManager; 394 } 395 396 // Components and single extension point loading 397 Collection<PluginIssue> errors = new ArrayList<>(); 398 _loadExtensionsPoints(manager, info.getExtensionPoints(), info.getExtensions(), contextPath, errors); 399 _loadComponents(manager, info.getComponents(), contextPath, errors); 400 _loadRuntimeInit(manager, errors); 401 402 _errors.addAll(errors); 403 404 if (!errors.isEmpty()) 405 { 406 _status = Status.NOT_INITIALIZED; 407 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 408 return safeManager; 409 } 410 411 _plugins = plugins; 412 _features = features; 413 _inactiveFeatures = info.getInactiveFeatures(); 414 _extensionPoints = info.getExtensionPoints(); 415 _extensions = info.getExtensions(); 416 _components = info.getComponents(); 417 418 try 419 { 420 manager.initialize(); 421 } 422 catch (Exception e) 423 { 424 _logger.error("Caught an exception loading components.", e); 425 426 _status = Status.NOT_INITIALIZED; 427 428 // Dispose the first ComponentManager 429 manager.dispose(); 430 manager = null; 431 432 // Then enter safe mode with another ComponentManager 433 PluginsComponentManager safeManager = _enterSafeMode(parentCM, context, contextPath); 434 return safeManager; 435 } 436 437 _status = Status.OK; 438 439 return manager; 440 } 441 442 /** 443 * Outputs the structure of the plugins. 444 * @param inactiveFeatures id and cause of inactive features 445 * @return a String representation of all existing plugins and features. 446 */ 447 public String dump(Map<String, InactivityCause> inactiveFeatures) 448 { 449 Collection<String> excludedPlugins = RuntimeConfig.getInstance().getExcludedPlugins(); 450 StringBuilder sb = new StringBuilder(); 451 452 for (String pluginName : _allPlugins.keySet()) 453 { 454 Plugin plugin = _allPlugins.get(pluginName); 455 sb.append(_dumpPlugin(plugin, excludedPlugins, inactiveFeatures)); 456 } 457 458 if (!_errors.isEmpty()) 459 { 460 sb.append("\nErrors :\n"); 461 _errors.forEach(issue -> sb.append(issue.toString()).append('\n')); 462 } 463 464 return sb.toString(); 465 } 466 467 private String _dumpPlugin(Plugin plugin, Collection<String> excludedPlugins, Map<String, InactivityCause> inactiveFeatures) 468 { 469 StringBuilder sb = new StringBuilder(); 470 471 String pluginName = plugin.getName(); 472 sb.append("Plugin ").append(pluginName); 473 474 if (excludedPlugins.contains(pluginName)) 475 { 476 sb.append(" *** excluded ***"); 477 } 478 479 sb.append('\n'); 480 481 Collection<String> configParameters = plugin.getConfigParameters().keySet(); 482 if (!CollectionUtils.isEmpty(configParameters)) 483 { 484 sb.append(" Config parameters : \n"); 485 configParameters.forEach(param -> sb.append(" ").append(param).append('\n')); 486 } 487 488 Collection<String> paramCheckers = plugin.getParameterCheckers().keySet(); 489 if (!CollectionUtils.isEmpty(paramCheckers)) 490 { 491 sb.append(" Parameters checkers : \n"); 492 paramCheckers.forEach(param -> sb.append(" ").append(param).append('\n')); 493 } 494 495 Collection<String> extensionPoints = plugin.getExtensionPoints(); 496 if (!CollectionUtils.isEmpty(extensionPoints)) 497 { 498 sb.append(" Extension points : \n"); 499 extensionPoints.forEach(point -> sb.append(" ").append(point).append('\n')); 500 } 501 502 Map<String, Feature> features = plugin.getFeatures(); 503 for (String featureId : features.keySet()) 504 { 505 Feature feature = features.get(featureId); 506 sb.append(_dumpFeature(feature, inactiveFeatures)); 507 } 508 509 sb.append('\n'); 510 511 return sb.toString(); 512 } 513 514 private String _dumpFeature(Feature feature, Map<String, InactivityCause> inactiveFeatures) 515 { 516 StringBuilder sb = new StringBuilder(); 517 String featureId = feature.getFeatureId(); 518 519 sb.append(" Feature ").append(featureId); 520 if (feature.isPassive()) 521 { 522 sb.append(" (passive)"); 523 } 524 525 if (feature.isSafe()) 526 { 527 sb.append(" (safe)"); 528 } 529 530 if (inactiveFeatures != null && inactiveFeatures.containsKey(featureId)) 531 { 532 sb.append(" *** inactive (").append(inactiveFeatures.get(featureId)).append(") ***"); 533 } 534 535 sb.append('\n'); 536 537 Collection<String> featureConfigParameters = feature.getConfigParameters().keySet(); 538 if (!CollectionUtils.isEmpty(featureConfigParameters)) 539 { 540 sb.append(" Config parameters : \n"); 541 featureConfigParameters.forEach(param -> sb.append(" ").append(param).append('\n')); 542 } 543 544 Collection<String> configParametersReferences = feature.getConfigParametersReferences(); 545 if (!CollectionUtils.isEmpty(configParametersReferences)) 546 { 547 sb.append(" Config parameters references : \n"); 548 configParametersReferences.forEach(param -> sb.append(" ").append(param).append('\n')); 549 } 550 551 Collection<String> featureParamCheckers = feature.getParameterCheckers().keySet(); 552 if (!CollectionUtils.isEmpty(featureParamCheckers)) 553 { 554 sb.append(" Parameters checkers : \n"); 555 featureParamCheckers.forEach(param -> sb.append(" ").append(param).append('\n')); 556 } 557 558 Map<String, String> componentsIds = feature.getComponentsIds(); 559 if (!componentsIds.isEmpty()) 560 { 561 sb.append(" Components : \n"); 562 563 for (String role : componentsIds.keySet()) 564 { 565 String id = componentsIds.get(role); 566 sb.append(" ").append(role).append(" : ").append(id).append('\n'); 567 } 568 569 sb.append('\n'); 570 } 571 572 Map<String, Collection<String>> extensionsIds = feature.getExtensionsIds(); 573 if (!extensionsIds.isEmpty()) 574 { 575 sb.append(" Extensions : \n"); 576 577 for (Entry<String, Collection<String>> extensionEntry : extensionsIds.entrySet()) 578 { 579 String point = extensionEntry.getKey(); 580 Collection<String> ids = extensionEntry.getValue(); 581 582 sb.append(" ").append(point).append(" :\n"); 583 ids.forEach(id -> sb.append(" ").append(id).append('\n')); 584 } 585 586 sb.append('\n'); 587 } 588 589 return sb.toString(); 590 } 591 592 // Look for plugins bundled in jars 593 // They have a META-INF/ametys-plugins plain text file containing plugin name and path to plugin.xml 594 private void _initResourceURIs() throws IOException 595 { 596 Enumeration<URL> pluginResources = getClass().getClassLoader().getResources("META-INF/ametys-plugins"); 597 598 while (pluginResources.hasMoreElements()) 599 { 600 URL pluginResource = pluginResources.nextElement(); 601 602 try (InputStream is = pluginResource.openStream(); 603 BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) 604 { 605 String plugin; 606 while ((plugin = br.readLine()) != null) 607 { 608 int i = plugin.indexOf(':'); 609 if (i != -1) 610 { 611 String pluginName = plugin.substring(0, i); 612 String pluginResourceURI = plugin.substring(i + 1); 613 614 _resourceURIs.put(pluginName, pluginResourceURI); 615 } 616 } 617 } 618 } 619 } 620 621 private Map<String, Plugin> _parsePlugins(String contextPath, Collection<String> locations, Map<String, File> externalPlugins, Collection<String> excludedPlugins) throws IOException 622 { 623 Map<String, Plugin> plugins = new HashMap<>(); 624 625 // Bundled plugins configurations loading 626 for (String pluginName : _resourceURIs.keySet()) 627 { 628 String resourceURI = _resourceURIs.get(pluginName) + "/" + PLUGIN_FILENAME; 629 630 if (getClass().getResource(resourceURI) == null) 631 { 632 _pluginError(pluginName, "A plugin '" + pluginName + "' is declared in a jar, but no file '" + PLUGIN_FILENAME + "' can be found at '" + resourceURI + "'.", PluginIssueCode.BUNDLED_PLUGIN_NOT_PRESENT, excludedPlugins, null); 633 } 634 else if (!pluginName.matches("^" + PLUGIN_NAME_REGEXP + "$")) 635 { 636 _pluginError(pluginName, pluginName + " is an incorrect plugin name.", PluginIssueCode.PLUGIN_NAME_INVALID, excludedPlugins, null); 637 } 638 else if (plugins.containsKey(pluginName)) 639 { 640 _pluginError(pluginName, "The plugin " + pluginName + " at " + resourceURI + " is already declared.", PluginIssueCode.PLUGIN_NAME_EXIST, excludedPlugins, null); 641 } 642 643 _logger.debug("Reading plugin configuration at {}", resourceURI); 644 645 Configuration configuration = null; 646 try (InputStream is = getClass().getResourceAsStream(resourceURI)) 647 { 648 configuration = _getConfigurationFromStream(pluginName, is, "resource:/" + resourceURI, excludedPlugins); 649 } 650 651 if (configuration != null) 652 { 653 Plugin plugin = new Plugin(pluginName); 654 plugin.configure(configuration); 655 plugins.put(pluginName, plugin); 656 657 _logger.info("Plugin '{}' found at path 'resource:/{}'", pluginName, resourceURI); 658 } 659 } 660 661 // Other plugins configuration loading 662 for (String location : locations) 663 { 664 File locationBase = new File(contextPath, location); 665 666 if (locationBase.exists() && locationBase.isDirectory()) 667 { 668 File[] pluginDirs = locationBase.listFiles(new FileFilter() 669 { 670 public boolean accept(File pathname) 671 { 672 return pathname.isDirectory(); 673 } 674 }); 675 676 for (File pluginDir : pluginDirs) 677 { 678 _addPlugin(plugins, pluginDir.getName(), pluginDir, excludedPlugins); 679 } 680 } 681 } 682 683 // external plugins 684 for (String externalPlugin : externalPlugins.keySet()) 685 { 686 File pluginDir = externalPlugins.get(externalPlugin); 687 688 if (pluginDir.exists() && pluginDir.isDirectory()) 689 { 690 _addPlugin(plugins, externalPlugin, pluginDir, excludedPlugins); 691 } 692 } 693 694 return plugins; 695 } 696 697 private void _addPlugin(Map<String, Plugin> plugins, String pluginName, File pluginDir, Collection<String> excludedPlugins) throws IOException 698 { 699 if (pluginName.matches(PLUGIN_NAMES_IGNORED)) 700 { 701 _logger.debug("Skipping directory {} ...", pluginDir.getAbsolutePath()); 702 return; 703 } 704 705 if (!pluginName.matches("^" + PLUGIN_NAME_REGEXP + "$")) 706 { 707 _logger.warn("{} is an incorrect plugin directory name. It will be ignored.", pluginName); 708 return; 709 } 710 711 File pluginFile = new File(pluginDir, PLUGIN_FILENAME); 712 if (!pluginFile.exists()) 713 { 714 _logger.warn("There is no file named {} in the directory {}. It will be ignored.", PLUGIN_FILENAME, pluginDir.getAbsolutePath()); 715 return; 716 } 717 718 if (plugins.containsKey(pluginName)) 719 { 720 _pluginError(pluginName, "The plugin " + pluginName + " at " + pluginFile.getAbsolutePath() + " is already declared.", PluginIssueCode.PLUGIN_NAME_EXIST, excludedPlugins, null); 721 return; 722 } 723 724 _logger.debug("Reading plugin configuration at {}", pluginFile.getAbsolutePath()); 725 726 Configuration configuration = null; 727 try (InputStream is = new FileInputStream(pluginFile)) 728 { 729 configuration = _getConfigurationFromStream(pluginName, is, pluginFile.getAbsolutePath(), excludedPlugins); 730 } 731 732 if (configuration != null) 733 { 734 Plugin plugin = new Plugin(pluginName); 735 plugin.configure(configuration); 736 plugins.put(pluginName, plugin); 737 738 _locations.put(pluginName, pluginDir); 739 _logger.info("Plugin '{}' found at path '{}'", pluginName, pluginFile.getAbsolutePath()); 740 } 741 } 742 743 private Configuration _getConfigurationFromStream(String pluginName, InputStream is, String path, Collection<String> excludedPlugins) 744 { 745 try 746 { 747 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 748 URL schemaURL = getClass().getResource("plugin-4.0.xsd"); 749 Schema schema = schemaFactory.newSchema(schemaURL); 750 SAXParserFactory factory = SAXParserFactory.newInstance(); 751 factory.setNamespaceAware(true); 752 factory.setSchema(schema); 753 XMLReader reader = factory.newSAXParser().getXMLReader(); 754 DefaultConfigurationBuilder confBuilder = new DefaultConfigurationBuilder(reader); 755 756 return confBuilder.build(is, path); 757 } 758 catch (Exception e) 759 { 760 _pluginError(pluginName, "Unable to access to plugin '" + pluginName + "' at " + path, PluginIssueCode.CONFIGURATION_UNREADABLE, excludedPlugins, e); 761 return null; 762 } 763 } 764 765 private void _pluginError(String pluginName, String message, PluginIssueCode code, Collection<String> excludedPlugins, Exception e) 766 { 767 // ignore errors for manually excluded plugins 768 if (!excludedPlugins.contains(pluginName)) 769 { 770 PluginIssue issue = new PluginIssue(null, null, code, null, message, e); 771 _errors.add(issue); 772 _logger.error(message, e); 773 } 774 } 775 776 /** 777 * Computes the actual plugins and features to load, based on values selected by the administrator.<br> 778 * This method don't actually load nor execute any Java code. It reads plugins definitions, selects active features and get components and extensions definitions. 779 * @param contextPath the application context path. 780 * @param excludedPlugins manually excluded plugins. 781 * @param excludedFeatures manually excluded features. 782 * @param componentsConfig chosen components, among those with the same role. 783 * @return all informations gathered during plugins reading. 784 */ 785 private PluginsInformation computeActiveFeatures(String contextPath, Collection<String> excludedPlugins, Collection<String> excludedFeatures, Map<String, String> componentsConfig) 786 { 787 Map<String, Feature> initialFeatures = new HashMap<>(); 788 Map<String, ExtensionPointDefinition> extensionPoints = new HashMap<>(); 789 Map<String, InactivityCause> inactiveFeatures = new HashMap<>(); 790 791 Collection<PluginIssue> errors = new ArrayList<>(); 792 793 // Get actual plugin list, corresponding extension points and initial feature list 794 Map<String, Plugin> plugins = _computeActivePlugins(excludedPlugins, initialFeatures, inactiveFeatures, extensionPoints, errors); 795 796 // Compute incoming deactivations 797 Map<String, Collection<String>> incomingDeactivations = _computeIncomingDeactivations(initialFeatures); 798 799 // First remove user-excluded features 800 Set<String> ids = initialFeatures.keySet(); 801 Iterator<String> it = ids.iterator(); 802 while (it.hasNext()) 803 { 804 String id = it.next(); 805 806 if (excludedFeatures.contains(id)) 807 { 808 _logger.debug("Remove excluded feature '{}'", id); 809 it.remove(); 810 inactiveFeatures.put(id, InactivityCause.EXCLUDED); 811 } 812 } 813 814 // Then remove deactivated features 815 // Also remove feature containing inactive components 816 _removeInactiveFeatures(initialFeatures, inactiveFeatures, incomingDeactivations, componentsConfig); 817 818 ids = initialFeatures.keySet(); 819 it = ids.iterator(); 820 while (it.hasNext()) 821 { 822 String id = it.next(); 823 Feature feature = initialFeatures.get(id); 824 Map<String, Collection<String>> extensionsIds = feature.getExtensionsIds(); 825 boolean hasBeenRemoved = false; 826 for (String point : extensionsIds.keySet()) 827 { 828 if (!extensionPoints.containsKey(point)) 829 { 830 String message = "In feature '" + id + "' an extension references the non-existing point '" + point + "'."; 831 _logger.error(message); 832 PluginIssue issue = new PluginIssue(feature.getPluginName(), feature.getFeatureName(), PluginIssueCode.INVALID_POINT, null, message); 833 errors.add(issue); 834 if (!hasBeenRemoved) 835 { 836 it.remove(); 837 inactiveFeatures.put(id, InactivityCause.INVALID_POINT); 838 hasBeenRemoved = true; 839 } 840 } 841 } 842 } 843 844 // Process outgoing dependencies 845 Map<String, Feature> features = _processOutgoingDependencies(initialFeatures, inactiveFeatures, errors); 846 847 // Compute incoming dependencies 848 Map<String, Collection<String>> incomingDependencies = _computeIncompingDependencies(features); 849 850 // Finally remove unused passive features 851 ids = features.keySet(); 852 it = ids.iterator(); 853 while (it.hasNext()) 854 { 855 String id = it.next(); 856 Feature feature = features.get(id); 857 858 if (feature.isPassive() && !incomingDependencies.containsKey(id)) 859 { 860 _logger.debug("Remove passive feature '{}'", id); 861 it.remove(); 862 inactiveFeatures.put(id, InactivityCause.PASSIVE); 863 } 864 } 865 866 // Check uniqueness of extensions and components 867 Map<String, Map<String, ExtensionDefinition>> extensions = _computeExtensions(features, errors); 868 Map<String, ComponentDefinition> components = _computeComponents(features, componentsConfig, errors); 869 870 return new PluginsInformation(plugins, features, inactiveFeatures, extensionPoints, extensions, components, errors); 871 } 872 873 private Map<String, Plugin> _computeActivePlugins(Collection<String> excludedPlugins, Map<String, Feature> initialFeatures, Map<String, InactivityCause> inactiveFeatures, Map<String, ExtensionPointDefinition> extensionPoints, Collection<PluginIssue> errors) 874 { 875 Map<String, Plugin> plugins = new HashMap<>(); 876 for (String pluginName : _allPlugins.keySet()) 877 { 878 if (!excludedPlugins.contains(pluginName)) 879 { 880 Plugin plugin = _allPlugins.get(pluginName); 881 plugins.put(pluginName, plugin); 882 _logger.info("Plugin '{}' loaded", pluginName); 883 884 // Check uniqueness of extension points 885 Map<String, ExtensionPointDefinition> extPoints = plugin.getExtensionPointDefinitions(); 886 for (String point : extPoints.keySet()) 887 { 888 ExtensionPointDefinition definition = extPoints.get(point); 889 890 if (!_safeMode || definition._safe) 891 { 892 if (extensionPoints.containsKey(point)) 893 { 894 // It is an error to have two extension points with the same id, but we should not interrupt when in safe mode, so just ignore it 895 String message = "The extension point '" + point + "', defined in the plugin '" + pluginName + "' is already defined in aother plugin. "; 896 PluginIssue issue = new PluginIssue(pluginName, null, PluginIssue.PluginIssueCode.EXTENSIONPOINT_ALREADY_EXIST, definition._configuration.getLocation(), message); 897 898 if (!_safeMode) 899 { 900 _logger.error(message); 901 errors.add(issue); 902 } 903 else 904 { 905 _logger.debug("[Safe mode] {}", message); 906 } 907 } 908 else 909 { 910 extensionPoints.put(point, definition); 911 } 912 } 913 } 914 915 Map<String, Feature> features = plugin.getFeatures(); 916 for (String id : features.keySet()) 917 { 918 Feature feature = features.get(id); 919 920 if (!_safeMode || feature.isSafe()) 921 { 922 initialFeatures.put(id, feature); 923 } 924 else 925 { 926 inactiveFeatures.put(id, InactivityCause.NOT_SAFE); 927 } 928 } 929 } 930 else 931 { 932 _logger.debug("Plugin '{}' is excluded", pluginName); 933 } 934 } 935 936 return plugins; 937 } 938 939 private void _removeInactiveFeatures(Map<String, Feature> initialFeatures, Map<String, InactivityCause> inactiveFeatures, Map<String, Collection<String>> incomingDeactivations, Map<String, String> componentsConfig) 940 { 941 Iterator<String> it = initialFeatures.keySet().iterator(); 942 while (it.hasNext()) 943 { 944 String id = it.next(); 945 Feature feature = initialFeatures.get(id); 946 947 if (incomingDeactivations.containsKey(id) && !incomingDeactivations.get(id).isEmpty()) 948 { 949 String deactivatingFeature = incomingDeactivations.get(id).iterator().next(); 950 _logger.debug("Removing feature {} deactivated by feature {}.", id, deactivatingFeature); 951 it.remove(); 952 inactiveFeatures.put(id, InactivityCause.DEACTIVATED); 953 continue; 954 } 955 956 Map<String, String> components = feature.getComponentsIds(); 957 for (String role : components.keySet()) 958 { 959 String componentId = components.get(role); 960 String selectedId = componentsConfig.get(role); 961 962 // remove the feature if the user asked for a specific id and the declared component has not that id 963 if (selectedId != null && !selectedId.equals(componentId)) 964 { 965 _logger.debug("Removing feature '{}' as it contains the component id '{}' for role '{}' but the user selected the id '{}' for that role.", id, componentId, role, selectedId); 966 it.remove(); 967 inactiveFeatures.put(id, InactivityCause.COMPONENT); 968 continue; 969 } 970 } 971 } 972 } 973 974 private Map<String, Collection<String>> _computeIncomingDeactivations(Map<String, Feature> features) 975 { 976 Map<String, Collection<String>> incomingDeactivations = new HashMap<>(); 977 978 for (String id : features.keySet()) 979 { 980 Feature feature = features.get(id); 981 Collection<String> deactivations = feature.getDeactivations(); 982 983 for (String deactivation : deactivations) 984 { 985 Collection<String> deps = incomingDeactivations.get(deactivation); 986 if (deps == null) 987 { 988 deps = new ArrayList<>(); 989 incomingDeactivations.put(deactivation, deps); 990 } 991 992 deps.add(id); 993 } 994 } 995 996 return incomingDeactivations; 997 } 998 999 private Map<String, Feature> _processOutgoingDependencies(Map<String, Feature> initialFeatures, Map<String, InactivityCause> inactiveFeatures, Collection<PluginIssue> errors) 1000 { 1001 // Check outgoing dependencies 1002 boolean processDependencies = true; 1003 while (processDependencies) 1004 { 1005 processDependencies = false; 1006 1007 Collection<String> ids = initialFeatures.keySet(); 1008 Iterator<String> it = ids.iterator(); 1009 while (it.hasNext()) 1010 { 1011 String id = it.next(); 1012 Feature feature = initialFeatures.get(id); 1013 Collection<String> dependencies = feature.getDependencies(); 1014 for (String dependency : dependencies) 1015 { 1016 if (!initialFeatures.containsKey(dependency)) 1017 { 1018 _logger.debug("The feature '{}' depends on '{}' which is not present. It will be ignored.", id, dependency); 1019 it.remove(); 1020 inactiveFeatures.put(id, InactivityCause.DEPENDENCY); 1021 processDependencies = true; 1022 } 1023 } 1024 } 1025 } 1026 1027 // Reorder remaining features, respecting dependencies 1028 LinkedHashMap<String, Feature> features = new LinkedHashMap<>(); 1029 1030 for (String featureId : initialFeatures.keySet()) 1031 { 1032 _computeFeaturesDependencies(featureId, initialFeatures, features, featureId, errors); 1033 } 1034 1035 return features; 1036 } 1037 1038 private Map<String, Collection<String>> _computeIncompingDependencies(Map<String, Feature> features) 1039 { 1040 Map<String, Collection<String>> incomingDependencies = new HashMap<>(); 1041 for (String id : features.keySet()) 1042 { 1043 Feature feature = features.get(id); 1044 Collection<String> dependencies = feature.getDependencies(); 1045 1046 for (String dependency : dependencies) 1047 { 1048 Collection<String> deps = incomingDependencies.get(dependency); 1049 if (deps == null) 1050 { 1051 deps = new ArrayList<>(); 1052 incomingDependencies.put(dependency, deps); 1053 } 1054 1055 deps.add(id); 1056 } 1057 } 1058 1059 return incomingDependencies; 1060 } 1061 1062 private void _computeFeaturesDependencies(String featureId, Map<String, Feature> features, Map<String, Feature> result, String initialFeatureId, Collection<PluginIssue> errors) 1063 { 1064 Feature feature = features.get(featureId); 1065 Collection<String> dependencies = feature.getDependencies(); 1066 1067 for (String dependency : dependencies) 1068 { 1069 if (initialFeatureId.equals(dependency)) 1070 { 1071 String message = "Circular dependency detected for feature: " + feature; 1072 _logger.error(message); 1073 PluginIssue issue = new PluginIssue(feature.getPluginName(), feature.getFeatureName(), PluginIssueCode.CIRCULAR_DEPENDENCY, null, message); 1074 errors.add(issue); 1075 } 1076 else if (!result.containsKey(dependency)) 1077 { 1078 // do not process the feature if it has already been processed 1079 _computeFeaturesDependencies(dependency, features, result, initialFeatureId, errors); 1080 } 1081 } 1082 1083 result.put(featureId, feature); 1084 } 1085 1086 private Map<String, Map<String, ExtensionDefinition>> _computeExtensions(Map<String, Feature> features, Collection<PluginIssue> errors) 1087 { 1088 Map<String, Map<String, ExtensionDefinition>> extensionsDefinitions = new HashMap<>(); 1089 for (Feature feature : features.values()) 1090 { 1091 // extensions 1092 Map<String, Map<String, ExtensionDefinition>> extensionsConfs = feature.getExtensions(); 1093 for (String point : extensionsConfs.keySet()) 1094 { 1095 Map<String, ExtensionDefinition> featureExtensions = extensionsConfs.get(point); 1096 Map<String, ExtensionDefinition> globalExtensions = extensionsDefinitions.get(point); 1097 if (globalExtensions == null) 1098 { 1099 globalExtensions = new LinkedHashMap<>(featureExtensions); 1100 extensionsDefinitions.put(point, globalExtensions); 1101 } 1102 else 1103 { 1104 for (String id : featureExtensions.keySet()) 1105 { 1106 if (globalExtensions.containsKey(id)) 1107 { 1108 String message = "The extension '" + id + "' to point '" + point + "' is already defined in another feature."; 1109 _logger.error(message); 1110 PluginIssue issue = new PluginIssue(feature.getPluginName(), feature.getFeatureName(), PluginIssueCode.EXTENSION_ALREADY_EXIST, null, message); 1111 errors.add(issue); 1112 } 1113 else 1114 { 1115 ExtensionDefinition definition = featureExtensions.get(id); 1116 globalExtensions.put(id, definition); 1117 } 1118 } 1119 } 1120 } 1121 } 1122 1123 return extensionsDefinitions; 1124 } 1125 1126 private Map<String, ComponentDefinition> _computeComponents(Map<String, Feature> features, Map<String, String> componentsConfig, Collection<PluginIssue> errors) 1127 { 1128 Map<String, ComponentDefinition> components = new HashMap<>(); 1129 1130 for (Feature feature : features.values()) 1131 { 1132 // components 1133 Map<String, ComponentDefinition> featureComponents = feature.getComponents(); 1134 for (String role : featureComponents.keySet()) 1135 { 1136 ComponentDefinition definition = featureComponents.get(role); 1137 ComponentDefinition globalDefinition = components.get(role); 1138 if (globalDefinition == null) 1139 { 1140 components.put(role, definition); 1141 } 1142 else 1143 { 1144 String id = definition.getId(); 1145 if (id.equals(globalDefinition.getId())) 1146 { 1147 String message = "The component for role '" + role + "' and id '" + id + "' is defined both in feature '" + definition.getPluginName() + FEATURE_ID_SEPARATOR + definition.getFeatureName() + "' and in feature '" + globalDefinition.getPluginName() + FEATURE_ID_SEPARATOR + globalDefinition.getFeatureName() + "'."; 1148 _logger.error(message); 1149 PluginIssue issue = new PluginIssue(feature.getPluginName(), feature.getFeatureName(), PluginIssueCode.COMPONENT_ALREADY_EXIST, null, message); 1150 errors.add(issue); 1151 } 1152 else 1153 { 1154 String message = "The component for role '" + role + "' is defined with id '" + id + "' in the feature '" + definition.getPluginName() + FEATURE_ID_SEPARATOR + definition.getFeatureName() + "' and with id '" + globalDefinition.getId() + "' in the feature '" + globalDefinition.getPluginName() + FEATURE_ID_SEPARATOR + globalDefinition.getFeatureName() + "'. One of them should be chosen in the runtime.xml."; 1155 _logger.error(message); 1156 PluginIssue issue = new PluginIssue(feature.getPluginName(), feature.getFeatureName(), PluginIssueCode.COMPONENT_ALREADY_EXIST, null, message); 1157 errors.add(issue); 1158 } 1159 } 1160 } 1161 } 1162 1163 // check that each component choosen in the runtime.xml is actually defined 1164 for (String role : componentsConfig.keySet()) 1165 { 1166 String requiredId = componentsConfig.get(role); 1167 ComponentDefinition definition = components.get(role); 1168 1169 if (definition == null || !definition.getId().equals(requiredId)) 1170 { 1171 // Due to preceding checks, the definition id should not be different than requiredId, but two checks are always better than one ... 1172 String message = "The component for role '" + role + "' should point to id '" + requiredId + "' but no component match."; 1173 _logger.error(message); 1174 PluginIssue issue = new PluginIssue(null, null, PluginIssueCode.COMPONENT_NOT_DECLARED, null, message); 1175 errors.add(issue); 1176 } 1177 } 1178 1179 return components; 1180 } 1181 1182 private void _loadExtensionsPoints(PluginsComponentManager manager, Map<String, ExtensionPointDefinition> extensionPoints, Map<String, Map<String, ExtensionDefinition>> extensionsDefinitions, String contextPath, Collection<PluginIssue> errors) 1183 { 1184 for (String point : extensionPoints.keySet()) 1185 { 1186 ExtensionPointDefinition definition = extensionPoints.get(point); 1187 Configuration conf = definition._configuration; 1188 String clazz = conf.getAttribute("class", null); 1189 String pluginName = definition._pluginName; 1190 1191 try 1192 { 1193 Class<? extends Object> c = Class.forName(clazz); 1194 1195 // check that the class is actually an ExtensionPoint 1196 if (ExtensionPoint.class.isAssignableFrom(c)) 1197 { 1198 Class<? extends ExtensionPoint> extensionClass = c.asSubclass(ExtensionPoint.class); 1199 1200 // Load extensions 1201 Collection<ExtensionDefinition> extensionDefinitions = new ArrayList<>(); 1202 Map<String, ExtensionDefinition> initialDefinitions = extensionsDefinitions.get(point); 1203 1204 if (initialDefinitions != null) 1205 { 1206 for (String id : initialDefinitions.keySet()) 1207 { 1208 ExtensionDefinition extensionDefinition = initialDefinitions.get(id); 1209 Configuration initialConf = extensionDefinition.getConfiguration(); 1210 Configuration realExtensionConf = _getComponentConfiguration(initialConf, contextPath, extensionDefinition.getPluginName(), errors); 1211 extensionDefinitions.add(new ExtensionDefinition(id, point, extensionDefinition.getPluginName(), extensionDefinition.getFeatureName(), realExtensionConf)); 1212 } 1213 } 1214 1215 Configuration realComponentConf = _getComponentConfiguration(conf, contextPath, pluginName, errors); 1216 manager.addExtensionPoint(pluginName, point, extensionClass, realComponentConf, extensionDefinitions); 1217 } 1218 else 1219 { 1220 String message = "In plugin '" + pluginName + "', the extension point '" + point + "' references class '" + clazz + "' which don't implement " + ExtensionPoint.class.getName(); 1221 _logger.error(message); 1222 PluginIssue issue = new PluginIssue(pluginName, null, PluginIssue.PluginIssueCode.EXTENSIONPOINT_CLASS_INVALID, conf.getLocation(), message); 1223 errors.add(issue); 1224 } 1225 } 1226 catch (ClassNotFoundException e) 1227 { 1228 String message = "In plugin '" + pluginName + "', the extension point '" + point + "' references the unexisting class '" + clazz + "'."; 1229 _logger.error(message, e); 1230 PluginIssue issue = new PluginIssue(pluginName, null, PluginIssue.PluginIssueCode.CLASSNOTFOUND, conf.getLocation(), message); 1231 errors.add(issue); 1232 } 1233 } 1234 } 1235 1236 @SuppressWarnings("unchecked") 1237 private void _loadComponents(PluginsComponentManager manager, Map<String, ComponentDefinition> components, String contextPath, Collection<PluginIssue> errors) 1238 { 1239 for (String role : components.keySet()) 1240 { 1241 ComponentDefinition componentDefinition = components.get(role); 1242 Configuration componentConf = componentDefinition.getConfiguration(); 1243 Configuration realComponentConf = _getComponentConfiguration(componentConf, contextPath, componentDefinition.getPluginName(), errors); 1244 1245 // XML schema ensures class is not null 1246 String clazz = componentConf.getAttribute("class", null); 1247 assert clazz != null; 1248 1249 try 1250 { 1251 Class c = Class.forName(clazz); 1252 manager.addComponent(componentDefinition.getPluginName(), componentDefinition.getFeatureName(), role, c, realComponentConf); 1253 } 1254 catch (ClassNotFoundException ex) 1255 { 1256 String message = "In feature '" + componentDefinition.getPluginName() + FEATURE_ID_SEPARATOR + componentDefinition.getFeatureName() + "', the component '" + role + "' references the unexisting class '" + clazz + "'."; 1257 _logger.error(message, ex); 1258 PluginIssue issue = new PluginIssue(componentDefinition.getPluginName(), componentDefinition.getFeatureName(), PluginIssueCode.CLASSNOTFOUND, componentConf.getLocation(), message); 1259 errors.add(issue); 1260 } 1261 } 1262 } 1263 1264 private Configuration _getComponentConfiguration(Configuration initialConfiguration, String contextPath, String pluginName, Collection<PluginIssue> errors) 1265 { 1266 String config = initialConfiguration.getAttribute("config", null); 1267 1268 if (config != null) 1269 { 1270 @SuppressWarnings("resource") InputStream is = null; 1271 String configPath = null; 1272 1273 try 1274 { 1275 // If the config attribute is present, it is either a plugin-relative, or a webapp-relative path (starting with '/') 1276 if (config.startsWith("/")) 1277 { 1278 // absolute path 1279 File configFile = new File(contextPath, config); 1280 configPath = configFile.getAbsolutePath(); 1281 1282 if (!configFile.exists() || configFile.isDirectory()) 1283 { 1284 if (_logger.isInfoEnabled()) 1285 { 1286 _logger.info("No config file was found at " + configPath + ". Using internally declared config."); 1287 } 1288 1289 return initialConfiguration; 1290 } 1291 1292 is = new FileInputStream(configFile); 1293 } 1294 else 1295 { 1296 // relative path 1297 String baseUri = _resourceURIs.get(pluginName); 1298 if (baseUri == null) 1299 { 1300 File pluginLocation = getPluginLocation(pluginName); 1301 1302 File configFile = new File(pluginLocation, config); 1303 configPath = configFile.getAbsolutePath(); 1304 1305 if (!configFile.exists() || configFile.isDirectory()) 1306 { 1307 if (_logger.isInfoEnabled()) 1308 { 1309 _logger.info("No config file was found at " + configPath + ". Using internally declared config."); 1310 } 1311 1312 return initialConfiguration; 1313 } 1314 1315 is = new FileInputStream(configFile); 1316 } 1317 else 1318 { 1319 String path = baseUri + "/" + config; 1320 configPath = "resource:/" + path; 1321 is = getClass().getResourceAsStream(path); 1322 1323 if (is == null) 1324 { 1325 if (_logger.isInfoEnabled()) 1326 { 1327 _logger.info("No config file was found at " + configPath + ". Using internally declared config."); 1328 } 1329 1330 return initialConfiguration; 1331 } 1332 } 1333 } 1334 1335 return new DefaultConfigurationBuilder(true).build(is, configPath); 1336 } 1337 catch (Exception ex) 1338 { 1339 String message = "Unable to load external configuration defined in the plugin " + pluginName; 1340 _logger.error(message, ex); 1341 PluginIssue issue = new PluginIssue(pluginName, null, PluginIssueCode.EXTERNAL_CONFIGURATION, initialConfiguration.getLocation(), message); 1342 errors.add(issue); 1343 } 1344 finally 1345 { 1346 IOUtils.closeQuietly(is); 1347 } 1348 } 1349 1350 return initialConfiguration; 1351 } 1352 1353 private void _loadRuntimeInit(PluginsComponentManager manager, Collection<PluginIssue> errors) 1354 { 1355 String className = RuntimeConfig.getInstance().getInitClassName(); 1356 1357 if (className != null) 1358 { 1359 _logger.info("Loading init class '{}' for application", className); 1360 1361 try 1362 { 1363 Class<?> initClass = Class.forName(className); 1364 if (!Init.class.isAssignableFrom(initClass)) 1365 { 1366 String message = "Provided init class " + initClass + " does not implement " + Init.class.getName(); 1367 _logger.error(message); 1368 PluginIssue issue = new PluginIssue(null, null, PluginIssue.PluginIssueCode.INIT_CLASS_INVALID, null, message); 1369 errors.add(issue); 1370 return; 1371 } 1372 1373 manager.addComponent(null, null, Init.ROLE, initClass, new DefaultConfiguration("component")); 1374 _logger.info("Init class {} loaded", className); 1375 } 1376 catch (ClassNotFoundException e) 1377 { 1378 String message = "The application init class '" + className + "' does not exist."; 1379 _logger.error(message, e); 1380 PluginIssue issue = new PluginIssue(null, null, PluginIssueCode.CLASSNOTFOUND, null, message); 1381 errors.add(issue); 1382 } 1383 1384 } 1385 else if (_logger.isInfoEnabled()) 1386 { 1387 _logger.info("No init class configured"); 1388 } 1389 } 1390 1391 private PluginsComponentManager _enterSafeMode(ComponentManager parentCM, Context context, String contextPath) 1392 { 1393 _logger.info("Entering safe mode due to previous errors ..."); 1394 _safeMode = true; 1395 1396 PluginsInformation info = computeActiveFeatures(contextPath, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_MAP); 1397 1398 _plugins = info.getPlugins(); 1399 _extensionPoints = info.getExtensionPoints(); 1400 _components = info.getComponents(); 1401 _extensions = info.getExtensions(); 1402 _features = info.getFeatures(); 1403 _inactiveFeatures = info.getInactiveFeatures(); 1404 1405 if (_logger.isDebugEnabled()) 1406 { 1407 _logger.debug("Safe mode : \n\n" + dump(_inactiveFeatures)); 1408 } 1409 1410 Collection<PluginIssue> errors = info.getErrors(); 1411 if (!errors.isEmpty()) 1412 { 1413 // errors while in safe mode ... 1414 throw new PluginException("Errors while loading components in safe mode.", _errors, errors); 1415 } 1416 1417 // Create the ComponentManager 1418 PluginsComponentManager manager = new PluginsComponentManager(parentCM); 1419 manager.setLogger(LoggerFactory.getLogger("org.ametys.runtime.plugin.manager")); 1420 manager.contextualize(context); 1421 1422 ConfigManager.getInstance().service(new WrapperServiceManager(manager)); 1423 1424 errors = new ArrayList<>(); 1425 _loadExtensionsPoints(manager, _extensionPoints, _extensions, contextPath, errors); 1426 _loadComponents(manager, _components, contextPath, errors); 1427 1428 if (!errors.isEmpty()) 1429 { 1430 // errors while in safe mode ... 1431 throw new PluginException("Errors while loading components in safe mode.", _errors, errors); 1432 } 1433 1434 try 1435 { 1436 manager.initialize(); 1437 } 1438 catch (Exception e) 1439 { 1440 throw new PluginException("Caught exception while starting ComponentManager in safe mode.", e, _errors, null); 1441 } 1442 1443 return manager; 1444 } 1445 1446 /** 1447 * Cause of the deactivation of a feature 1448 */ 1449 public enum InactivityCause 1450 { 1451 /** 1452 * Constant for excluded features 1453 */ 1454 EXCLUDED, 1455 1456 /** 1457 * Constant for features deactivated by other features 1458 */ 1459 DEACTIVATED, 1460 1461 /** 1462 * Constant for features disabled due to not choosen component 1463 */ 1464 COMPONENT, 1465 1466 /** 1467 * Constant for features disabled due to missing dependencies 1468 */ 1469 DEPENDENCY, 1470 1471 /** 1472 * Constant for passive features that are not necessary (nobody depends on it) 1473 */ 1474 PASSIVE, 1475 1476 /** 1477 * Constant for features disabled to wrong referenced extension point 1478 */ 1479 INVALID_POINT, 1480 1481 /** 1482 * Feature is not safe while in safe mode 1483 */ 1484 NOT_SAFE 1485 } 1486 1487 /** 1488 * PluginsManager status after initialization. 1489 */ 1490 public enum Status 1491 { 1492 /** 1493 * Everything is ok. All features were correctly loaded. 1494 */ 1495 OK, 1496 1497 /** 1498 * There was no errors, but the configuration is missing. 1499 */ 1500 NO_CONFIG, 1501 1502 /** 1503 * There was no errors, but the configuration is incomplete. 1504 */ 1505 CONFIG_INCOMPLETE, 1506 1507 /** 1508 * Something was wrong when reading plugins definitions. 1509 */ 1510 WRONG_DEFINITIONS, 1511 1512 /** 1513 * There were issues during components loading. 1514 */ 1515 NOT_INITIALIZED, 1516 1517 /** 1518 * The runtime.xml could not be loaded. 1519 */ 1520 RUNTIME_NOT_LOADED, 1521 1522 /** 1523 * Safe mode has been forced. 1524 */ 1525 SAFE_MODE_FORCED 1526 } 1527 1528 /** 1529 * Helper class containing all relevant informations after features list computation. 1530 */ 1531 public static class PluginsInformation 1532 { 1533 private Map<String, Plugin> _plugins; 1534 private Map<String, Feature> _features; 1535 private Map<String, InactivityCause> _inactiveFeatures; 1536 private Map<String, ExtensionPointDefinition> _extensionPoints; 1537 private Map<String, Map<String, ExtensionDefinition>> _extensions; 1538 private Map<String, ComponentDefinition> _components; 1539 private Collection<PluginIssue> _errors; 1540 1541 PluginsInformation(Map<String, Plugin> plugins, Map<String, Feature> features, Map<String, InactivityCause> inactiveFeatures, Map<String, ExtensionPointDefinition> extensionPoints, Map<String, Map<String, ExtensionDefinition>> extensions, Map<String, ComponentDefinition> components, Collection<PluginIssue> errors) 1542 { 1543 _plugins = plugins; 1544 _features = features; 1545 _inactiveFeatures = inactiveFeatures; 1546 _extensionPoints = extensionPoints; 1547 _extensions = extensions; 1548 _components = components; 1549 _errors = errors; 1550 } 1551 1552 Map<String, Plugin> getPlugins() 1553 { 1554 return _plugins; 1555 } 1556 1557 Map<String, Feature> getFeatures() 1558 { 1559 return _features; 1560 } 1561 1562 Map<String, InactivityCause> getInactiveFeatures() 1563 { 1564 return _inactiveFeatures; 1565 } 1566 1567 Map<String, ExtensionPointDefinition> getExtensionPoints() 1568 { 1569 return _extensionPoints; 1570 } 1571 1572 Map<String, Map<String, ExtensionDefinition>> getExtensions() 1573 { 1574 return _extensions; 1575 } 1576 1577 Map<String, ComponentDefinition> getComponents() 1578 { 1579 return _components; 1580 } 1581 1582 /** 1583 * Returns all errors collected during initialization phase. 1584 * @return all errors collected during initialization phase. 1585 */ 1586 public Collection<PluginIssue> getErrors() 1587 { 1588 return _errors; 1589 } 1590 } 1591}