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