001/* 002 * Copyright 2018 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.config; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.OutputStream; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030 031import javax.xml.transform.OutputKeys; 032import javax.xml.transform.TransformerFactory; 033import javax.xml.transform.TransformerFactoryConfigurationError; 034import javax.xml.transform.sax.SAXTransformerFactory; 035import javax.xml.transform.sax.TransformerHandler; 036import javax.xml.transform.stream.StreamResult; 037 038import org.apache.avalon.framework.activity.Initializable; 039import org.apache.avalon.framework.configuration.ConfigurationException; 040import org.apache.avalon.framework.context.Context; 041import org.apache.avalon.framework.context.Contextualizable; 042import org.apache.avalon.framework.service.ServiceException; 043import org.apache.avalon.framework.service.ServiceManager; 044import org.apache.avalon.framework.service.Serviceable; 045import org.apache.cocoon.xml.XMLUtils; 046import org.apache.commons.lang3.StringUtils; 047import org.apache.xml.serializer.OutputPropertiesFactory; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050import org.xml.sax.SAXException; 051 052import org.ametys.core.util.I18nUtils; 053import org.ametys.core.util.I18nizableTextComparator; 054import org.ametys.core.util.I18nizableTextKeyComparator; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.model.CategorizedElementDefinitionHelper; 057import org.ametys.runtime.model.DefinitionAndValue; 058import org.ametys.runtime.model.ElementDefinition; 059import org.ametys.runtime.model.Enumerator; 060import org.ametys.runtime.model.I18nizableTextModelItemComparator; 061import org.ametys.runtime.model.Model; 062import org.ametys.runtime.model.ModelHelper; 063import org.ametys.runtime.model.ModelItem; 064import org.ametys.runtime.model.ModelItemGroup; 065import org.ametys.runtime.model.View; 066import org.ametys.runtime.model.checker.ItemChecker; 067import org.ametys.runtime.model.disableconditions.DefaultDisableConditionsEvaluator; 068import org.ametys.runtime.model.disableconditions.DisableConditions; 069import org.ametys.runtime.model.disableconditions.DisableConditionsEvaluator; 070import org.ametys.runtime.model.exception.UndefinedItemPathException; 071import org.ametys.runtime.model.type.ElementType; 072import org.ametys.runtime.model.type.ModelItemTypeConstants; 073import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint; 074import org.ametys.runtime.model.type.xml.XMLElementType; 075import org.ametys.runtime.parameter.ValidationResult; 076import org.ametys.runtime.parameter.Validator; 077import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 078 079/** 080 * This manager handle the parameters of the application that have to be stored by the plugins. 081 */ 082public final class ConfigManager implements Model, Contextualizable, Serviceable, Initializable 083{ 084 // shared instance 085 private static ConfigManager __manager; 086 087 // Logger for traces 088 private Logger _logger = LoggerFactory.getLogger(ConfigManager.class); 089 private Logger _threadSafeComponentLogger = LoggerFactory.getLogger("runtime.plugin.threadsafecomponent"); 090 091 // Avalon stuff 092 private ServiceManager _serviceManager; 093 private Context _context; 094 095 // ComponentManager for the disable conditions 096 private ThreadSafeComponentManager<DisableConditions> _disableConditionsManager; 097 // ComponentManager for the validators 098 private ThreadSafeComponentManager<Validator> _validatorManager; 099 // ComponentManager for the enumerators 100 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 101 // ComponentManager for the parameter checkers 102 private ThreadSafeComponentManager<ItemChecker> _parameterCheckerManager; 103 104 private Map<String, ConfigParameterInfo> _declaredParams; 105 private Map<String, ConfigParameterInfo> _declaredParamCheckers; 106 private Collection<String> _usedParamIds; 107 private Map<String, ConfigParameterDefinitionWrapper> _configParameterWrappers; 108 private Map<String, ConfigParameterCheckerDescriptor> _parameterCheckers; 109 110 // The configuration model items 111 private List<ModelItem> _categorizedDefinitions; 112 private Map<String, ElementDefinition> _flatDefinitions; 113 114 // Determines if the extension point is initialized 115 private boolean _isInitialized; 116 // Determines if all parameters are valued 117 private boolean _isComplete; 118 119 private ModelItemTypeExtensionPoint _configParameterTypeEP; 120 private DisableConditionsEvaluator _disableConditionsEvaluator; 121 122 private ConfigManager() 123 { 124 // empty constructor 125 } 126 127 /** 128 * Returns the shared instance of the ConfigManager 129 * @return the shared instance of the ConfigManager 130 */ 131 public static ConfigManager getInstance() 132 { 133 if (__manager == null) 134 { 135 __manager = new ConfigManager(); 136 } 137 138 return __manager; 139 } 140 141 @Override 142 public void contextualize(Context context) 143 { 144 _context = context; 145 } 146 147 @Override 148 public void service(ServiceManager manager) throws ServiceException 149 { 150 _serviceManager = manager; 151 _configParameterTypeEP = (ModelItemTypeExtensionPoint) _serviceManager.lookup(ModelItemTypeExtensionPoint.ROLE_CONFIG); 152 _disableConditionsEvaluator = (DisableConditionsEvaluator) _serviceManager.lookup(DefaultDisableConditionsEvaluator.ROLE); 153 } 154 155 @Override 156 public void initialize() 157 { 158 _usedParamIds = new ArrayList<>(); 159 _declaredParams = new HashMap<>(); 160 _configParameterWrappers = new HashMap<>(); 161 _declaredParamCheckers = new HashMap<>(); 162 _parameterCheckers = new HashMap<>(); 163 _flatDefinitions = new HashMap<>(); 164 165 _disableConditionsManager = new ThreadSafeComponentManager<>(); 166 _disableConditionsManager.setLogger(_threadSafeComponentLogger); 167 _disableConditionsManager.contextualize(_context); 168 _disableConditionsManager.service(_serviceManager); 169 170 _validatorManager = new ThreadSafeComponentManager<>(); 171 _validatorManager.setLogger(_threadSafeComponentLogger); 172 _validatorManager.contextualize(_context); 173 _validatorManager.service(_serviceManager); 174 175 _enumeratorManager = new ThreadSafeComponentManager<>(); 176 _enumeratorManager.setLogger(_threadSafeComponentLogger); 177 _enumeratorManager.contextualize(_context); 178 _enumeratorManager.service(_serviceManager); 179 180 _parameterCheckerManager = new ThreadSafeComponentManager<>(); 181 _parameterCheckerManager.setLogger(_threadSafeComponentLogger); 182 _parameterCheckerManager.contextualize(_context); 183 _parameterCheckerManager.service(_serviceManager); 184 } 185 186 /** 187 * Registers new available parameters. 188 * The addFeatureConfig() method allows to select which ones are actually useful. 189 * @param pluginName the name of the plugin defining the parameters 190 * @param parameters the configuration parameters definition 191 * @param parameterCheckers the parameters checkers definition 192 */ 193 public void addPluginConfig(String pluginName, Map<String, ConfigParameterInfo> parameters, Map<String, ConfigParameterInfo> parameterCheckers) 194 { 195 _logger.debug("Adding parameters and parameters checkers for plugin {}.", pluginName); 196 197 // declare parameters and parameter checkers configured in the plugin 198 _declareParameters(parameters); 199 _declareParameterCheckers(parameterCheckers); 200 } 201 202 /** 203 * Registers a new parameter or references a globalConfig parameter.<br> 204 * @param featureId the id of the feature defining the parameters 205 * @param parameters the configuration parameters definition 206 * @param parameterCheckers the parameters checkers definition 207 * @param parameterReferences references to already defined parameters 208 */ 209 public void addFeatureConfig(String featureId, Map<String, ConfigParameterInfo> parameters, Map<String, ConfigParameterInfo> parameterCheckers, Collection<String> parameterReferences) 210 { 211 _logger.debug("Selecting parameters for feature {}.", featureId); 212 213 // declare parameters and parameter checkers configured in the feature 214 _declareParameters(parameters); 215 _declareParameterCheckers(parameterCheckers); 216 217 // Add parameters declared in feature to the list of used parameters 218 _usedParamIds.addAll(parameters.keySet()); 219 220 // Add referenced parameters to the list of used parameters 221 _usedParamIds.addAll(parameterReferences); 222 } 223 224 private void _declareParameters(Map<String, ConfigParameterInfo> parameters) 225 { 226 for (String id : parameters.keySet()) 227 { 228 ConfigParameterInfo info = parameters.get(id); 229 230 // Check if the parameter is not already declared 231 if (_declaredParams.containsKey(id)) 232 { 233 throw new IllegalArgumentException("The config parameter '" + id + "' is already declared. Parameters ids must be unique"); 234 } 235 236 // Add the new parameter to the list of declared ones 237 _declaredParams.put(id, info); 238 239 _logger.debug("Parameter added: {}", id); 240 } 241 242 _logger.debug("{} parameter(s) added", parameters.size()); 243 } 244 245 private void _declareParameterCheckers(Map<String, ConfigParameterInfo> parameterCheckers) 246 { 247 for (String id : parameterCheckers.keySet()) 248 { 249 ConfigParameterInfo info = parameterCheckers.get(id); 250 251 // Check if the parameter checker is not already declared 252 if (_declaredParamCheckers.containsKey(id)) 253 { 254 throw new IllegalArgumentException("The config parameter checker '" + id + "' is already declared. Parameter checkers ids must be unique."); 255 } 256 257 // Add the new parameter checker to the list of declared ones 258 _declaredParamCheckers.put(id, info); 259 260 _logger.debug("Parameter checker added: {}", id); 261 } 262 263 _logger.debug("{} parameter checker(s) added", parameterCheckers.size()); 264 } 265 266 /** 267 * Ends the initialization of the config parameters, by checking against the 268 * already valued parameters.<br> 269 * If at least one parameter has no value, the application won't start. 270 */ 271 public void parseAndValidate() 272 { 273 _logger.debug("Initialization"); 274 275 _isInitialized = false; 276 _isComplete = true; 277 278 ConfigParameterDefinitionParser parser = new ConfigParameterDefinitionParser(_configParameterTypeEP, _disableConditionsManager, _enumeratorManager, _validatorManager); 279 _parseParameters(parser); 280 281 ConfigParameterCheckerParser parameterCheckerParser = new ConfigParameterCheckerParser(_parameterCheckerManager); 282 _parseParameterCheckers(parameterCheckerParser); 283 284 _categorizeParameters(); 285 286 try 287 { 288 parser.lookupComponents(); 289 parameterCheckerParser.lookupComponents(); 290 } 291 catch (Exception e) 292 { 293 throw new RuntimeException("Unable to lookup parameter local components", e); 294 } 295 296 _validateParametersForReading(); 297 298 _usedParamIds.clear(); 299 _declaredParams.clear(); 300 _declaredParamCheckers.clear(); 301 302 _isInitialized = true; 303 304 Config.setInitialized(_isComplete); 305 306 _logger.debug("Initialization ended"); 307 } 308 309 private void _parseParameters(ConfigParameterDefinitionParser definitionParser) 310 { 311 ConfigParameterDefinitionWrapperParser parser = new ConfigParameterDefinitionWrapperParser(definitionParser); 312 for (String id : _usedParamIds) 313 { 314 // Check if the parameter is not already used 315 if (_configParameterWrappers.get(id) == null) 316 { 317 // Move the parameter from the unused list, to the used list 318 ConfigParameterInfo parameterInfo = _declaredParams.get(id); 319 320 if (parameterInfo == null) 321 { 322 throw new RuntimeException("The parameter '" + id + "' is used but not declared"); 323 } 324 325 ConfigParameterDefinitionWrapper configParameterWrapper = null; 326 327 try 328 { 329 configParameterWrapper = parser.parse(_serviceManager, parameterInfo.getPluginName(), parameterInfo.getConfiguration(), getInstance(), null); 330 } 331 catch (ConfigurationException ex) 332 { 333 throw new RuntimeException("Unable to configure the config parameter : " + id, ex); 334 } 335 336 _configParameterWrappers.put(id, configParameterWrapper); 337 } 338 } 339 } 340 341 private void _parseParameterCheckers(ConfigParameterCheckerParser parameterCheckerParser) 342 { 343 for (String id : _declaredParamCheckers.keySet()) 344 { 345 boolean invalidParameters = false; 346 347 // Check if the parameter checker is not already used 348 if (_parameterCheckers.get(id) == null) 349 { 350 ConfigParameterInfo info = _declaredParamCheckers.get(id); 351 352 ConfigParameterCheckerDescriptor parameterChecker = null; 353 try 354 { 355 356 parameterChecker = parameterCheckerParser.parseParameterChecker(info.getPluginName(), info.getConfiguration()); 357 } 358 catch (ConfigurationException ex) 359 { 360 throw new RuntimeException("Unable to configure the parameter checker: " + id, ex); 361 } 362 363 for (String linkedParameterPath : parameterChecker.getLinkedParamsPaths()) 364 { 365 ConfigParameterDefinitionWrapper linkedParameter = null; 366 367 // Linked parameters can be declared with an absolute path, in which case they are prefixed with '/ 368 if (linkedParameterPath.startsWith(ModelItem.ITEM_PATH_SEPARATOR)) 369 { 370 linkedParameter = _configParameterWrappers.get(linkedParameterPath.substring(ModelItem.ITEM_PATH_SEPARATOR.length())); 371 } 372 else 373 { 374 linkedParameter = _configParameterWrappers.get(linkedParameterPath); 375 } 376 377 // If at least one parameter used is invalid, the parameter checker is invalidated 378 if (linkedParameter == null) 379 { 380 invalidParameters = true; 381 break; 382 } 383 } 384 385 if (invalidParameters) 386 { 387 _logger.debug("All the configuration parameters associated to the parameter checker '{}' are not used.\nThis parameter checker will be ignored.", parameterChecker.getName()); 388 } 389 else 390 { 391 _parameterCheckers.put(id, parameterChecker); 392 } 393 } 394 } 395 } 396 397 private void _categorizeParameters() 398 { 399 Collection<ConfigParameterDefinitionWrapper> categorizedParameterProxiesValues = _configParameterWrappers.values(); 400 _categorizedDefinitions = ConfigParameterDefinitionHelper.categorizeConfigParameters(categorizedParameterProxiesValues); 401 _flatDefinitions = ConfigParameterDefinitionHelper.getFlatDefinitions(categorizedParameterProxiesValues); 402 403 // Add parameter checkers to categories, groups and element definitions 404 _addParameterCheckersToModelItems(); 405 } 406 407 private void _addParameterCheckersToModelItems() 408 { 409 for (ConfigParameterCheckerDescriptor parameterChecker: _parameterCheckers.values()) 410 { 411 I18nizableText uiCategory = parameterChecker.getUiRefCategory(); 412 if (uiCategory != null) 413 { 414 ModelItemGroup category = _getModelItemGroup(_categorizedDefinitions, uiCategory); 415 if (category == null) 416 { 417 _logger.warn("The category {} doesn't exist, thus the parameter checker {} will not be added.", uiCategory, parameterChecker.getName()); 418 } 419 else 420 { 421 I18nizableText uiGroup = parameterChecker.getUiRefGroup(); 422 if (uiGroup == null) 423 { 424 category.addItemChecker(parameterChecker); 425 } 426 else 427 { 428 ModelItemGroup group = _getModelItemGroup(category.getChildren(), uiGroup); 429 if (group == null) 430 { 431 _logger.warn("The group {} doesn't exist, thus the parameter checker {} will not be added.", uiGroup, parameterChecker.getName()); 432 } 433 else 434 { 435 group.addItemChecker(parameterChecker); 436 } 437 } 438 } 439 } 440 else 441 { 442 String uiParameterId = parameterChecker.getUiRefParamId(); 443 if (uiParameterId != null) 444 { 445 ElementDefinition definition = _flatDefinitions.get(uiParameterId); 446 if (definition == null) 447 { 448 _logger.warn("The parameter {} doesn't exist, thus the parameter checker {} will not be added.", uiParameterId, parameterChecker.getName()); 449 } 450 else 451 { 452 definition.addItemChecker(parameterChecker); 453 } 454 } 455 } 456 } 457 } 458 459 private ModelItemGroup _getModelItemGroup(List<ModelItem> items, I18nizableText itemGroupLabel) 460 { 461 for (ModelItem item : items) 462 { 463 if (itemGroupLabel.equals(item.getLabel()) && item instanceof ModelItemGroup) 464 { 465 return (ModelItemGroup) item; 466 } 467 } 468 469 return null; 470 } 471 472 private void _validateParametersForReading() 473 { 474 // Dispose potential previous parameters 475 Config.dispose(); 476 477 // Get configuration values 478 Map<String, Object> values = null; 479 try 480 { 481 Config.setModel(this); 482 Map<String, DefinitionAndValue> definitionAndValues = Config.__read(); 483 values = Config.__extractValues(definitionAndValues); 484 } 485 catch (Exception e) 486 { 487 _logger.error("Cannot read the configuration file.", e); 488 _isComplete = false; 489 } 490 491 if (_isComplete && values != null) 492 { 493 for (ElementDefinition definition : _flatDefinitions.values()) 494 { 495 boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(definition, values); 496 boolean isDisabled = _disableConditionsEvaluator.evaluateDisableConditions(definition, definition.getName(), values); 497 498 if (isGroupSwitchOn && !isDisabled) 499 { 500 if (!values.containsKey(definition.getName())) 501 { 502 _logger.warn("The parameter '" + definition.getName() + "' is not valued. Configuration is not initialized."); 503 _isComplete = false; 504 } 505 else 506 { 507 Object value = values.get(definition.getName()); 508 ValidationResult result = ModelHelper.validateValue(definition, value); 509 if (result.hasErrors()) 510 { 511 if (_logger.isWarnEnabled()) 512 { 513 StringBuilder sb = new StringBuilder("The parameter '" + definition.getName() + "' is not valid with value '" + value + "' :"); 514 515 for (I18nizableText error : result.getErrors()) 516 { 517 sb.append("\n* " + error.toString()); 518 } 519 sb.append("\nConfiguration is not initialized"); 520 521 _logger.warn(sb.toString()); 522 } 523 524 _isComplete = false; 525 } 526 } 527 528 } 529 } 530 531 } 532 } 533 534 /** 535 * Update the configuration file with the given values<br> 536 * Values are untyped (all are of type String) and might be null. 537 * @param values A map (key, value). 538 * @param fileName the configuration file absolute path 539 * @return errors The fields in error 540 * @throws Exception If an error occurred while saving values 541 */ 542 public Map<String, List<I18nizableText>> save(Map<String, Object> values, String fileName) throws Exception 543 { 544 // Retrieve the old values for password purposes 545 Map<String, Object> oldValues = getOldValues(); 546 547 // Resolve each value and associate it to its definition 548 Map<String, Object> resolvedValues = _resolveValues(values, oldValues); 549 550 // Validate parameters 551 Map<String, List<I18nizableText>> errorFields = CategorizedElementDefinitionHelper.validateValuesForWriting(resolvedValues, _flatDefinitions.values(), _disableConditionsEvaluator, _logger) 552 .getAllErrors(); 553 if (!errorFields.isEmpty()) 554 { 555 return errorFields; 556 } 557 558 // SAX 559 _saxConfigurationFile(fileName, resolvedValues); 560 561 return Collections.EMPTY_MAP; 562 } 563 564 /** 565 * Retrieves old values if the configuration is not well initialized 566 * @return the old values 567 */ 568 public Map<String, Object> getOldValues() 569 { 570 if (Config.getInstance() == null) 571 { 572 try 573 { 574 Map<String, DefinitionAndValue> oldDefinitionAndValues = Config.__read(); 575 return Config.__extractValues(oldDefinitionAndValues); 576 } 577 catch (Exception e) 578 { 579 return new HashMap<>(); 580 } 581 } 582 583 return null; 584 } 585 586 private Map<String, Object> _resolveValues(Map<String, Object> values, Map<String, Object> oldValues) 587 { 588 Map<String, Object> resolvedValues = new HashMap<>(); 589 for (Map.Entry<String, Object> value : values.entrySet()) 590 { 591 ElementDefinition definition = _flatDefinitions.get(value.getKey()); 592 if (definition != null) 593 { 594 Object resolvedValue = _resolveValue(definition, value.getValue(), oldValues); 595 resolvedValues.put(value.getKey(), resolvedValue); 596 } 597 } 598 return resolvedValues; 599 } 600 601 /** 602 * Resolve the given value for the parameter check 603 * @param parameterName name of the parameter to resolve 604 * @param value the value to resolve 605 * @param oldValues the old values if the configuration is not initialized 606 * @return the resolved value as a String 607 */ 608 public String resolveValueForParameterChecker(String parameterName, Object value, Map<String, Object> oldValues) 609 { 610 ElementDefinition definition = _flatDefinitions.get(parameterName); 611 612 if (definition == null) 613 { 614 return null; 615 } 616 617 Object resolvedValue = _resolveValue(definition, value, oldValues); 618 return definition.getType().toString(resolvedValue); 619 } 620 621 private Object _resolveValue(ElementDefinition definition, Object value, Map<String, Object> oldValues) 622 { 623 String parameterId = definition.getName(); 624 ElementType type = definition.getType(); 625 Object resolvedValue = value; 626 627 // keeps the value of an empty password field 628 if (value == null && ModelItemTypeConstants.PASSWORD_ELEMENT_TYPE_ID.equals(type.getId())) 629 { 630 if (Config.getInstance() != null) 631 { 632 resolvedValue = Config.getInstance().getValue(parameterId); 633 } 634 else if (oldValues != null) 635 { 636 resolvedValue = oldValues.get(definition.getName()); 637 } 638 } 639 640 return resolvedValue; 641 } 642 643 private void _saxConfigurationFile(String fileName, Map<String, Object> values) throws TransformerFactoryConfigurationError, Exception 644 { 645 // create the result where to write 646 File outputFile = new File(fileName); 647 outputFile.getParentFile().mkdirs(); 648 649 try (OutputStream os = new FileOutputStream(fileName)) 650 { 651 // create a transformer for saving sax into a file 652 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 653 654 StreamResult result = new StreamResult(os); 655 th.setResult(result); 656 657 // create the format of result 658 Properties format = new Properties(); 659 format.put(OutputKeys.METHOD, "xml"); 660 format.put(OutputKeys.INDENT, "yes"); 661 format.put(OutputKeys.ENCODING, "UTF-8"); 662 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); 663 th.getTransformer().setOutputProperties(format); 664 665 // sax the configuration into the transformer 666 _saxParameters(th, values); 667 } 668 catch (Exception e) 669 { 670 throw new Exception("An error occured while saving the config values.", e); 671 } 672 } 673 674 private void _saxParameters(TransformerHandler handler, Map<String, Object> values) throws SAXException 675 { 676 handler.startDocument(); 677 XMLUtils.startElement(handler, "config"); 678 679 680 // Iterate over categorized parameters 681 for (ModelItem category : _categorizedDefinitions) 682 { 683 if (!(category instanceof ModelItemGroup)) 684 { 685 // Should not happen, categories are created in categorizeParameters method 686 continue; 687 } 688 689 StringBuilder categoryLabel = new StringBuilder(); 690 categoryLabel.append("+\n | "); 691 categoryLabel.append(category.getLabel().toString()); 692 categoryLabel.append("\n +"); 693 694 // Comment on current category 695 XMLUtils.data(handler, "\n "); 696 handler.comment(categoryLabel.toString().toCharArray(), 0, categoryLabel.length()); 697 XMLUtils.data(handler, "\n"); 698 XMLUtils.data(handler, "\n"); 699 700 Iterator<ModelItem> groups = ((ModelItemGroup) category).getChildren().iterator(); 701 while (groups.hasNext()) 702 { 703 ModelItem group = groups.next(); 704 if (!(group instanceof ModelItemGroup)) 705 { 706 // Should not happen, groups are created in categorizeParameters method 707 continue; 708 } 709 710 StringBuilder groupLabel = new StringBuilder(); 711 groupLabel.append(" "); 712 groupLabel.append(group.getLabel().toString()); 713 groupLabel.append(" "); 714 715 // Comment on current group 716 XMLUtils.data(handler, " "); 717 handler.comment(groupLabel.toString().toCharArray(), 0, groupLabel.length()); 718 XMLUtils.data(handler, "\n "); 719 720 for (ModelItem definition : ((ModelItemGroup) group).getChildren()) 721 { 722 if (!(definition instanceof ElementDefinition)) 723 { 724 // Should not happen, parameter references are created in categorizeParameters method 725 continue; 726 } 727 728 Object value = values.get(definition.getName()); 729 ElementType<Object> type = ((ElementDefinition) definition).getType(); 730 731 if (!(type instanceof XMLElementType)) 732 { 733 // Should not happen, configuration parameters only work with XML element types 734 continue; 735 } 736 737 ((XMLElementType<Object>) type).write(handler, definition.getName(), value); 738 } 739 740 if (groups.hasNext()) 741 { 742 XMLUtils.data(handler, "\n"); 743 } 744 } 745 746 XMLUtils.data(handler, "\n"); 747 } 748 749 XMLUtils.endElement(handler, "config"); 750 handler.endDocument(); 751 } 752 753 /** 754 * Evaluate the {@link DisableConditions} of the given configuration parameter against the configuration values 755 * @param definition the configuration parameter 756 * @param config the {@link Config} instance 757 * @return <code>true</code> if the parameter is disabled, <code>false</code> otherwise 758 */ 759 public boolean evaluateDisableConditions(ElementDefinition definition, Config config) 760 { 761 return _disableConditionsEvaluator.evaluateDisableConditions(definition, definition.getName(), config); 762 } 763 764 public ModelItem getModelItem(String itemPath) throws UndefinedItemPathException 765 { 766 ModelItem item = _flatDefinitions.get(itemPath); 767 if (item != null) 768 { 769 return item; 770 } 771 else 772 { 773 throw new UndefinedItemPathException("The parameter '" + itemPath + "' is not defined in the configuration."); 774 } 775 } 776 777 /** 778 * Retrieves all configuration parameters 779 * @return all configuration parameters 780 */ 781 public Collection<ElementDefinition> getConfigurationParameters() 782 { 783 return _flatDefinitions.values(); 784 } 785 786 public Collection<ModelItem> getModelItems() 787 { 788 return _categorizedDefinitions; 789 } 790 791 /** 792 * Retrieves the view of Configuration 793 * @param i18nUtils the i18nUtils to use categories's sorting 794 * @return the configuration's view 795 */ 796 public View getView(I18nUtils i18nUtils) 797 { 798 Comparator<ModelItem> categoriesComparator = new I18nizableTextModelItemComparator(new I18nizableTextComparator(i18nUtils)); 799 Comparator<ModelItem> groupsComparator = new I18nizableTextModelItemComparator(new I18nizableTextKeyComparator()); 800 Comparator<ModelItem> elementsComparator = new ConfigParameterDefinitionComparator(_configParameterWrappers.values()); 801 802 return ConfigParameterDefinitionHelper.buildConfigParametersView(_categorizedDefinitions, categoriesComparator, groupsComparator, elementsComparator); 803 } 804 805 /** 806 * Gets the parameter checker with its id 807 * @param id the id of the parameter checker to get 808 * @return the associated parameter checker descriptor 809 */ 810 public ConfigParameterCheckerDescriptor getParameterChecker(String id) 811 { 812 return _parameterCheckers.get(id); 813 } 814 815 /** 816 * Returns true if the model is initialized and all parameters are valued 817 * @return true if the model is initialized and all parameters are valued 818 */ 819 public boolean isComplete() 820 { 821 return _isInitialized && _isComplete; 822 } 823 824 /** 825 * Returns true if the config file does not exist 826 * @return true if the config file does not exist 827 */ 828 public boolean isEmpty() 829 { 830 return !Config.fileExists(); 831 } 832 833 /** 834 * Dispose the manager before restarting it 835 */ 836 public void dispose() 837 { 838 _isInitialized = false; 839 _isComplete = true; 840 841 _usedParamIds = null; 842 _declaredParams = null; 843 _configParameterWrappers = null; 844 _declaredParamCheckers = null; 845 _parameterCheckers = null; 846 _flatDefinitions = null; 847 848 if (_disableConditionsManager != null) 849 { 850 _disableConditionsManager.dispose(); 851 _disableConditionsManager = null; 852 } 853 if (_validatorManager != null) 854 { 855 _validatorManager.dispose(); 856 _validatorManager = null; 857 } 858 if (_enumeratorManager != null) 859 { 860 _enumeratorManager.dispose(); 861 _enumeratorManager = null; 862 } 863 if (_parameterCheckerManager != null) 864 { 865 _parameterCheckerManager.dispose(); 866 _parameterCheckerManager = null; 867 } 868 } 869 870 public String getId() 871 { 872 return StringUtils.EMPTY; 873 } 874 875 public String getFamilyId() 876 { 877 return this.getClass().getName(); 878 } 879}