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