001/*
002 *  Copyright 2016 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.core.group;
017
018import java.io.File;
019import java.io.FileOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.nio.file.Files;
023import java.nio.file.StandardCopyOption;
024import java.time.Instant;
025import java.time.temporal.ChronoUnit;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Optional;
033import java.util.Properties;
034import java.util.Set;
035import java.util.regex.Pattern;
036
037import javax.xml.transform.OutputKeys;
038import javax.xml.transform.TransformerConfigurationException;
039import javax.xml.transform.TransformerFactory;
040import javax.xml.transform.TransformerFactoryConfigurationError;
041import javax.xml.transform.sax.SAXTransformerFactory;
042import javax.xml.transform.sax.TransformerHandler;
043import javax.xml.transform.stream.StreamResult;
044
045import org.apache.avalon.framework.activity.Disposable;
046import org.apache.avalon.framework.activity.Initializable;
047import org.apache.avalon.framework.component.Component;
048import org.apache.avalon.framework.configuration.Configuration;
049import org.apache.avalon.framework.configuration.ConfigurationException;
050import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
051import org.apache.avalon.framework.service.ServiceException;
052import org.apache.avalon.framework.service.ServiceManager;
053import org.apache.avalon.framework.service.Serviceable;
054import org.apache.cocoon.components.LifecycleHelper;
055import org.apache.cocoon.xml.AttributesImpl;
056import org.apache.cocoon.xml.XMLUtils;
057import org.apache.xml.serializer.OutputPropertiesFactory;
058import org.xml.sax.SAXException;
059
060import org.ametys.core.group.directory.GroupDirectory;
061import org.ametys.core.group.directory.GroupDirectoryFactory;
062import org.ametys.core.group.directory.GroupDirectoryModel;
063import org.ametys.core.ui.Callable;
064import org.ametys.core.util.I18nUtils;
065import org.ametys.core.util.ReadXMLDataHelper;
066import org.ametys.runtime.i18n.I18nizableText;
067import org.ametys.runtime.model.DefinitionContext;
068import org.ametys.runtime.model.ElementDefinition;
069import org.ametys.runtime.model.type.ElementType;
070import org.ametys.runtime.model.type.xml.XMLElementType;
071import org.ametys.runtime.plugin.PluginsManager;
072import org.ametys.runtime.plugin.PluginsManager.Status;
073import org.ametys.runtime.plugin.component.AbstractLogEnabled;
074import org.ametys.runtime.util.AmetysHomeHelper;
075
076/**
077 * DAO for accessing {@link GroupDirectory}
078 */
079public class GroupDirectoryDAO extends AbstractLogEnabled implements Component, Initializable, Serviceable, Disposable
080{
081    /** Avalon Role */
082    public static final String ROLE = GroupDirectoryDAO.class.getName();
083    
084    /** The path of the XML file containing the group directories */
085    private static File __GROUP_DIRECTORIES_FILE;
086    
087    /** The regular expression for an id of a group directory */
088    private static final String __ID_REGEX = "^[a-z][a-z0-9_-]*";
089    
090    /** The date (as a long) of the last time the {@link #__GROUP_DIRECTORIES_FILE GroupDirectories file} was read (last update) */
091    private long _lastFileReading;
092    
093    /** The whole group directories of the application */
094    private Map<String, GroupDirectory> _groupDirectories;
095    
096    /** The factory for group directories */
097    private GroupDirectoryFactory _groupDirectoryFactory;
098    
099    /** the helper to read XML data */
100    private ReadXMLDataHelper _readXMLDataHelper;
101    
102    /** the i18n utils class */
103    private I18nUtils _i18nUtils;
104    
105    @Override
106    public void initialize()
107    {
108        __GROUP_DIRECTORIES_FILE = new File(AmetysHomeHelper.getAmetysHome(), "config" + File.separator + "group-directories.xml");
109        _groupDirectories = new LinkedHashMap<>();
110        _lastFileReading = 0;
111    }
112    
113    @Override
114    public void service(ServiceManager manager) throws ServiceException
115    {
116        _groupDirectoryFactory = (GroupDirectoryFactory) manager.lookup(GroupDirectoryFactory.ROLE);
117        _readXMLDataHelper = (ReadXMLDataHelper) manager.lookup(ReadXMLDataHelper.ROLE);
118        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
119    }
120    
121    /**
122     * Gets all the group directories to JSON format
123     * @return A list of object representing the {@link GroupDirectory GroupDirectories}
124     */
125    public List<Object> getGroupDirectories2Json()
126    {
127        List<Object> result = new ArrayList<>();
128        for (GroupDirectory groupDirectory : getGroupDirectories())
129        {
130            result.add(getGroupDirectory2Json(groupDirectory));
131        }
132        return result;
133    }
134    
135    /**
136     * gets a group directory to JSON format
137     * @param groupDirectory The group directory to get
138     * @return An object representing a {@link GroupDirectory}
139     */
140    public Map<String, Object> getGroupDirectory2Json(GroupDirectory groupDirectory)
141    {
142        Map<String, Object> result = new LinkedHashMap<>();
143        result.put("id", groupDirectory.getId());
144        result.put("label", groupDirectory.getLabel());
145        String modelId = groupDirectory.getGroupDirectoryModelId();
146        GroupDirectoryModel model = _groupDirectoryFactory.getExtension(modelId);
147        result.put("modelLabel", model.getLabel());
148        return result;
149    }
150    
151    /**
152     * Gets all the group directories of this application
153     * @return A list of {@link GroupDirectory GroupDirectories}
154     */
155    public List<GroupDirectory> getGroupDirectories()
156    {
157        // Don't read in safe mode, we know that only the admin population is needed in this case and we want to prevent some warnings in the logs for non-safe features not found
158        if (Status.OK.equals(PluginsManager.getInstance().getStatus()))
159        {
160            _read(false);
161            return new ArrayList<>(_groupDirectories.values());
162        }
163        else
164        {
165            return Collections.EMPTY_LIST;
166        }
167    }
168    
169    /**
170     * Gets a group directory by its id.
171     * @param id The id of the group directory
172     * @return The {@link GroupDirectory}, or null if not found
173     */
174    public GroupDirectory getGroupDirectory(String id)
175    {
176        _read(false);
177        return _groupDirectories.get(id);
178    }
179    
180    /**
181     * Gets the list of the ids of all the group directories of the application
182     * @return The list of the ids of all the group directories
183     */
184    @Callable
185    public Set<String> getGroupDirectoriesIds()
186    {
187        _read(false);
188        return _groupDirectories.keySet();
189    }
190    
191    /**
192     * Gets the configuration for creating/editing a group directory.
193     * @return A map containing information about what is needed to create/edit a group directory
194     * @throws Exception If an error occurs.
195     */
196    @Callable
197    public Map<String, Object> getEditionConfiguration() throws Exception
198    {
199        Map<String, Object> result = new LinkedHashMap<>();
200        
201        List<Object> groupDirectoryModels = new ArrayList<>();
202        for (String extensionId : _groupDirectoryFactory.getExtensionsIds())
203        {
204            GroupDirectoryModel model = _groupDirectoryFactory.getExtension(extensionId);
205            Map<String, Object> gdMap = new LinkedHashMap<>();
206            gdMap.put("id", extensionId);
207            gdMap.put("label", model.getLabel());
208            gdMap.put("description", model.getDescription());
209            
210            Map<String, Object> params = new LinkedHashMap<>();
211            for (String paramId : model.getParameters().keySet())
212            {
213                // prefix in case of two parameters from two different models have the same id which can lead to some errorsin client-side
214                params.put(extensionId + "$" + paramId, model.getParameters().get(paramId).toJSON(DefinitionContext.newInstance().withEdition(true)));
215            }
216            gdMap.put("parameters", params);
217            
218            groupDirectoryModels.add(gdMap);
219        }
220        result.put("groupDirectoryModels", groupDirectoryModels);
221        
222        return result;
223    }
224    
225    /**
226     * Gets the values of the parameters of the given group directory
227     * @param id The id of the group directory
228     * @return The values of the parameters
229     */
230    @Callable
231    public Map<String, Object> getGroupDirectoryParameterValues(String id)
232    {
233        Map<String, Object> result = new LinkedHashMap<>();
234        
235        _read(false);
236        GroupDirectory gd = _groupDirectories.get(id);
237        
238        if (gd == null)
239        {
240            getLogger().error("The GroupDirectory of id '{}' does not exist.", id);
241            result.put("error", "unknown");
242            return result;
243        }
244        
245        result.put("label", gd.getLabel());
246        result.put("id", gd.getId());
247        String modelId = gd.getGroupDirectoryModelId();
248        result.put("modelId", modelId);
249        Map<String, Object> params = new HashMap<>();
250        for (String key : gd.getParameterValues().keySet())
251        {
252            params.put(modelId + "$" + key, gd.getParameterValues().get(key));
253        }
254        result.put("params", params);
255        
256        return result;
257    }
258    
259    /**
260     * Adds a new group directory
261     * @param id The unique id of the group directory
262     * @param label The label of the group directory
263     * @param modelId The id of the group directory model
264     * @param params The parameters of the group directory
265     * @return A map containing the id of the created group directory, or the kind of error that occured
266     */
267    @Callable
268    public Map<String, Object> add(String id, String label, String modelId, Map<String, String> params)
269    {
270        _read(false);
271        
272        Map<String, Object> result = new LinkedHashMap<>();
273        
274        if (!_isCorrectId(id))
275        {
276            return null;
277        }
278        
279        GroupDirectory gd = _createGroupDirectory(id, label, modelId, params);
280        if (gd == null)
281        {
282            getLogger().error("An error occured when creating the GroupDirectory with id '{}'. See previous logs for more information.", id);
283            result.put("error", "server");
284            return result;
285        }
286        
287        _groupDirectories.put(id, gd);
288        if (_write())
289        {
290            getLogger().error("An error occured when writing the configuration file which contains the group directories.", id);
291            result.put("error", "server");
292            return result;
293        }
294        
295        result.put("id", id);
296        return result;
297    }
298    
299    private boolean _isCorrectId(String id)
300    {
301        if (_groupDirectories.get(id) != null)
302        {
303            getLogger().error("The id '{}' is already used for a group directory.", id);
304            return false;
305        }
306        
307        if (!Pattern.matches(__ID_REGEX, id))
308        {
309            getLogger().error("The id '{}' is not a correct id for a group directory.", id);
310            return false;
311        }
312        
313        return true;
314    }
315    
316    /**
317     * Edits the given group directory
318     * @param id The id of the group directory to edit
319     * @param label The label of the group directory
320     * @param modelId The id of the group directory model
321     * @param params The parameters of the group directory
322     * @return A map containing the id of the edited group directory, or the kind of error that occured
323     */
324    @Callable
325    public Map<String, Object> edit(String id, String label, String modelId, Map<String, String> params)
326    {
327        _read(false);
328        
329        Map<String, Object> result = new LinkedHashMap<>();
330        
331        GroupDirectory gd = _groupDirectories.get(id);
332        if (gd == null)
333        {
334            getLogger().error("The GroupDirectory with id '{}' does not exist, it cannot be edited.", id);
335            result.put("error", "unknown");
336            return result;
337        }
338        else
339        {
340            GroupDirectory removedGroupDirectory = _groupDirectories.remove(id);
341            LifecycleHelper.dispose(removedGroupDirectory);
342        }
343        
344        GroupDirectory newGd = _createGroupDirectory(id, label, modelId, params);
345        if (newGd == null)
346        {
347            getLogger().error("An error occured when editing the GroupDirectory with id '{}'. See previous logs for more information.", id);
348            result.put("error", "server");
349            return result;
350        }
351        
352        _groupDirectories.put(id, newGd);
353        if (_write())
354        {
355            getLogger().error("An error occured when writing the configuration file which contains the group directories.", id);
356            result.put("error", "server");
357            return result;
358        }
359        
360        result.put("id", id);
361        return result;
362    }
363    
364    private GroupDirectory _createGroupDirectory(String id, String label, String modelId, Map<String, String> params)
365    {
366        Map<String, Object> typedParams = _getTypedParams(params, modelId);
367        return _groupDirectoryFactory.createGroupDirectory(id, new I18nizableText(label), modelId, typedParams);
368    }
369    
370    private Map<String, Object> _getTypedParams(Map<String, String> params, String modelId)
371    {
372        Map<String, Object> resultParameters = new LinkedHashMap<>();
373        
374        Map<String, ? extends ElementDefinition> declaredParameters = _groupDirectoryFactory.getExtension(modelId).getParameters();
375        for (String paramNameWithPrefix : params.keySet())
376        {
377            String[] splitStr = paramNameWithPrefix.split("\\$", 2);
378            String prefix = splitStr[0];
379            String paramName = splitStr[1];
380            if (prefix.equals(modelId) && declaredParameters.containsKey(paramName))
381            {
382                String originalValue = params.get(paramNameWithPrefix);
383                
384                ElementDefinition parameter = declaredParameters.get(paramName);
385                ElementType type = parameter.getType();
386                
387                Object typedValue = type.castValue(originalValue);
388                resultParameters.put(paramName, typedValue);
389            }
390            else if (prefix.equals(modelId))
391            {
392                getLogger().warn("The parameter {} is not declared in extension {}. It will be ignored", paramName, modelId);
393            }
394        }
395        
396        return resultParameters;
397    }
398    
399    /**
400     * Removes the given group directory
401     * @param id The id of the group directory to remove
402     * @return A map containing the id of the removed group directory
403     */
404    @Callable
405    public Map<String, Object> remove(String id)
406    {
407        Map<String, Object> result = new LinkedHashMap<>();
408        
409        _read(false);
410        GroupDirectory removedGroupDirectory = _groupDirectories.remove(id);
411        if (removedGroupDirectory == null)
412        {
413            getLogger().error("The GroupDirectory with id '{}' does not exist, it cannot be removed.", id);
414            result.put("error", "unknown");
415            return result;
416        }
417        
418        LifecycleHelper.dispose(removedGroupDirectory);
419        
420        if (_write())
421        {
422            return null;
423        }
424        
425        result.put("id", id);
426        return result;
427    }
428    
429    /**
430     * If needed, reads the config file representing the group directories and then
431     * reinitializes and updates the internal representation of the group directories.
432     * @param forceRead True to avoid the use of the cache and force the reading of the file
433     */
434    private synchronized void _read(boolean forceRead)
435    {
436        try
437        {
438            if (!__GROUP_DIRECTORIES_FILE.exists())
439            {
440                _createDirectoriesFile(__GROUP_DIRECTORIES_FILE);
441            }
442            
443            // In Linux file systems, the precision of java.io.File.lastModified() is the second, so we need here to always have
444            // this (bad!) precision by doing the truncation to second precision (/1000 * 1000) on the millis time value.
445            // Therefore, the boolean outdated is computed with '>=' operator, and not '>', which will lead to sometimes (but rarely) unnecessarily re-read the file.
446            long fileLastModified = (__GROUP_DIRECTORIES_FILE.lastModified() / 1000) * 1000;
447            if (forceRead || fileLastModified >= _lastFileReading)
448            {
449                long lastFileReading = Instant.now().truncatedTo(ChronoUnit.SECONDS).toEpochMilli();
450                Map<String, GroupDirectory> groupDirectories = new LinkedHashMap<>();
451                
452                Configuration cfg = new DefaultConfigurationBuilder().buildFromFile(__GROUP_DIRECTORIES_FILE);
453                for (Configuration childCfg : cfg.getChildren("groupDirectory"))
454                {
455                    try
456                    {
457                        GroupDirectory groupDirectory = _configureGroupDirectory(childCfg);
458                        if (groupDirectory != null)
459                        {
460                            groupDirectories.put(groupDirectory.getId(), groupDirectory);
461                        }
462                    }
463                    catch (ConfigurationException e)
464                    {
465                        getLogger().error("Error configuring the group directory '" + childCfg.getAttribute("id", "") + "'. The group directory will be ignored.", e);
466                    }
467                }
468                
469                // Release previous components
470                this.dispose();
471                
472                _lastFileReading = lastFileReading;
473                _groupDirectories = groupDirectories;
474            }
475        }
476        catch (IOException | TransformerConfigurationException | ConfigurationException | SAXException e)
477        {
478            if (getLogger().isErrorEnabled())
479            {
480                getLogger().error("Error retrieving group directories with the configuration file " + __GROUP_DIRECTORIES_FILE, e);
481            }
482        }
483    }
484    
485    private void _createDirectoriesFile(File file) throws IOException, TransformerConfigurationException, SAXException
486    {
487        file.createNewFile();
488        try (OutputStream os = new FileOutputStream(file))
489        {
490            // create a transformer for saving sax into a file
491            TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler();
492            
493            StreamResult result = new StreamResult(os);
494            th.setResult(result);
495
496            // create the format of result
497            Properties format = new Properties();
498            format.put(OutputKeys.METHOD, "xml");
499            format.put(OutputKeys.INDENT, "yes");
500            format.put(OutputKeys.ENCODING, "UTF-8");
501            format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4");
502            th.getTransformer().setOutputProperties(format);
503            th.startDocument();
504            XMLUtils.createElement(th, "groupDirectories");
505            th.endDocument();
506        }
507    }
508    
509    private GroupDirectory _configureGroupDirectory(Configuration configuration) throws ConfigurationException
510    {
511        String id = configuration.getAttribute("id");
512        String modelId = configuration.getAttribute("modelId");
513        I18nizableText label = new I18nizableText(configuration.getChild("label").getValue());
514        Map<String, Object> paramValues = _getParametersFromConfiguration(configuration.getChild("params"), modelId, id);
515        if (paramValues != null)
516        {
517            GroupDirectory gd = _groupDirectoryFactory.createGroupDirectory(id, label, modelId, paramValues);
518            if (gd != null)
519            {
520                return gd;
521            }
522        }
523        
524        return null;
525    }
526    
527    private Map<String, Object> _getParametersFromConfiguration(Configuration conf, String modelId, String groupId) throws ConfigurationException
528    {
529        if (!_groupDirectoryFactory.hasExtension(modelId))
530        {
531            getLogger().warn("The model id '{}' is referenced in the file containing the group directories but seems to not exist.", modelId);
532            return null;
533        }
534        
535        Map<String, ? extends ElementDefinition> declaredParameters = _groupDirectoryFactory.getExtension(modelId).getParameters();
536        
537        Map<String, List<I18nizableText>> errors = new HashMap<>();
538        Map<String, Object> parameters = _readXMLDataHelper.readAndValidateXMLData(conf, modelId + "$", declaredParameters, errors);
539        if (errors.isEmpty())
540        {
541            return parameters;
542        }
543        else
544        {
545            StringBuilder sb = new StringBuilder(String.format("The group directory of id '%s' declares a model with id '%s' but some parameters are not valid:", groupId, modelId));
546            for (String dataName : errors.keySet())
547            {
548                for (I18nizableText error : errors.get(dataName))
549                {
550                    sb.append("\n* '").append(dataName).append("': ").append(_i18nUtils.translate(error));
551                }
552            }
553            sb.append("\nThis group directory will be ignored.");
554            
555            throw new ConfigurationException(sb.toString(), conf);
556        }
557    }
558    
559    /**
560     * Erases the config file representing the group directories and rebuild it 
561     * from the internal representation of the group directories.
562     * @return True if an error occurred
563     */
564    private boolean _write()
565    {
566        File backup = new File(__GROUP_DIRECTORIES_FILE.getPath() + ".tmp");
567        boolean errorOccured = false;
568        
569        // Create a backup file
570        try
571        {
572            Files.copy(__GROUP_DIRECTORIES_FILE.toPath(), backup.toPath());
573        }
574        catch (IOException e)
575        {
576            if (getLogger().isErrorEnabled())
577            {
578                getLogger().error("Error when creating backup '" + __GROUP_DIRECTORIES_FILE + "' file", e);
579            }
580        }
581        
582        // Do writing
583        try (OutputStream os = new FileOutputStream(__GROUP_DIRECTORIES_FILE))
584        {
585            // create a transformer for saving sax into a file
586            TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler();
587            
588            StreamResult result = new StreamResult(os);
589            th.setResult(result);
590
591            // create the format of result
592            Properties format = new Properties();
593            format.put(OutputKeys.METHOD, "xml");
594            format.put(OutputKeys.INDENT, "yes");
595            format.put(OutputKeys.ENCODING, "UTF-8");
596            format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4");
597            th.getTransformer().setOutputProperties(format);
598
599            // sax the config
600            try
601            {
602                _toSAX(th);
603            }
604            catch (Exception e)
605            {
606                if (getLogger().isErrorEnabled())
607                {
608                    getLogger().error("Error when saxing the groupDirectories", e);
609                }
610                errorOccured = true;
611            }
612        }
613        catch (IOException | TransformerConfigurationException | TransformerFactoryConfigurationError e)
614        {
615            if (getLogger().isErrorEnabled())
616            {
617                getLogger().error("Error when trying to modify the group directories with the configuration file " + __GROUP_DIRECTORIES_FILE, e);
618            }
619        }
620        
621        // Restore the file if an error previously occured
622        try
623        {
624            if (errorOccured)
625            {
626                // An error occured, restore the original
627                Files.copy(backup.toPath(), __GROUP_DIRECTORIES_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING);
628                // Force to reread the file
629                _read(true);
630            }
631            Files.deleteIfExists(backup.toPath());
632        }
633        catch (IOException e)
634        {
635            if (getLogger().isErrorEnabled())
636            {
637                getLogger().error("Error when restoring backup '" + __GROUP_DIRECTORIES_FILE + "' file", e);
638            }
639        }
640        
641        return errorOccured;
642    }
643    
644    private void _toSAX(TransformerHandler handler) throws SAXException
645    {
646        handler.startDocument();
647        XMLUtils.startElement(handler, "groupDirectories");
648        for (GroupDirectory gd : _groupDirectories.values())
649        {
650            _saxGroupDirectory(gd, handler);
651        }
652        
653        XMLUtils.endElement(handler, "groupDirectories");
654        handler.endDocument();
655    }
656    
657    private void _saxGroupDirectory(GroupDirectory groupDirectory, TransformerHandler handler) throws SAXException
658    {
659        AttributesImpl atts = new AttributesImpl();
660        atts.addCDATAAttribute("id", groupDirectory.getId());
661        atts.addCDATAAttribute("modelId", groupDirectory.getGroupDirectoryModelId());
662        XMLUtils.startElement(handler, "groupDirectory", atts);
663        
664        groupDirectory.getLabel().toSAX(handler, "label");
665        
666        XMLUtils.startElement(handler, "params");
667        
668        GroupDirectoryModel groupDirectoryModel = _groupDirectoryFactory.getExtension(groupDirectory.getGroupDirectoryModelId());
669        _writeParameterValues(groupDirectoryModel.getParameters(), groupDirectory.getParameterValues(), handler);
670        
671        XMLUtils.endElement(handler, "params");
672        
673        XMLUtils.endElement(handler, "groupDirectory");
674    }
675    
676    private void _writeParameterValues(Map<String, ? extends ElementDefinition> definitions, Map<String, Object> paramValues, TransformerHandler handler) throws SAXException
677    {
678        for (String paramName : paramValues.keySet())
679        {
680            Object value = paramValues.get(paramName);
681            ElementDefinition definition = definitions.get(paramName);
682            
683            Optional<XMLElementType> type = Optional.ofNullable(definition)
684                    .map(ElementDefinition::getType)
685                    .filter(XMLElementType.class::isInstance)
686                    .map(XMLElementType.class::cast);
687            
688            if (type.isPresent())
689            {
690                type.get().write(handler, paramName, value);
691            }
692        }
693    }
694    
695    @Override
696    public void dispose()
697    {
698        for (GroupDirectory gd : _groupDirectories.values())
699        {
700            LifecycleHelper.dispose(gd);
701        }
702        _lastFileReading = 0;
703    }
704}