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