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}