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