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}