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