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.plugins.contentio.synchronize; 017 018import java.io.File; 019import java.io.FileNotFoundException; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.nio.file.Files; 024import java.nio.file.StandardCopyOption; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Properties; 032 033import javax.xml.transform.OutputKeys; 034import javax.xml.transform.TransformerConfigurationException; 035import javax.xml.transform.TransformerFactory; 036import javax.xml.transform.TransformerFactoryConfigurationError; 037import javax.xml.transform.sax.SAXTransformerFactory; 038import javax.xml.transform.sax.TransformerHandler; 039import javax.xml.transform.stream.StreamResult; 040 041import org.apache.avalon.framework.activity.Disposable; 042import org.apache.avalon.framework.activity.Initializable; 043import org.apache.avalon.framework.component.Component; 044import org.apache.avalon.framework.configuration.Configuration; 045import org.apache.avalon.framework.configuration.ConfigurationException; 046import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 047import org.apache.avalon.framework.context.Context; 048import org.apache.avalon.framework.context.ContextException; 049import org.apache.avalon.framework.context.Contextualizable; 050import org.apache.avalon.framework.service.ServiceException; 051import org.apache.avalon.framework.service.ServiceManager; 052import org.apache.avalon.framework.service.Serviceable; 053import org.apache.cocoon.ProcessingException; 054import org.apache.cocoon.components.LifecycleHelper; 055import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 056import org.apache.cocoon.xml.AttributesImpl; 057import org.apache.cocoon.xml.XMLUtils; 058import org.apache.xml.serializer.OutputPropertiesFactory; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061import org.xml.sax.ContentHandler; 062import org.xml.sax.SAXException; 063 064import org.ametys.cms.contenttype.ContentType; 065import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 066import org.ametys.core.ui.Callable; 067import org.ametys.core.user.directory.UserDirectory; 068import org.ametys.core.user.directory.UserDirectoryFactory; 069import org.ametys.core.user.directory.UserDirectoryModel; 070import org.ametys.core.user.population.UserPopulation; 071import org.ametys.core.user.population.UserPopulationDAO; 072import org.ametys.plugins.contentio.synchronize.impl.DefaultSynchronizingContentOperator; 073import org.ametys.plugins.core.impl.user.directory.JdbcUserDirectory; 074import org.ametys.plugins.core.impl.user.directory.LdapUserDirectory; 075import org.ametys.plugins.workflow.support.WorkflowProvider; 076import org.ametys.runtime.i18n.I18nizableText; 077import org.ametys.runtime.parameter.ParameterHelper; 078import org.ametys.runtime.plugin.component.AbstractLogEnabled; 079import org.ametys.runtime.plugin.component.LogEnabled; 080import org.ametys.runtime.util.AmetysHomeHelper; 081 082/** 083 * DAO for accessing {@link SynchronizableContentsCollection} 084 */ 085public class SynchronizableContentsCollectionDAO extends AbstractLogEnabled implements Component, Serviceable, Initializable, Contextualizable, Disposable 086{ 087 /** Avalon Role */ 088 public static final String ROLE = SynchronizableContentsCollectionDAO.class.getName(); 089 090 private static final File __CONFIGURATION_FILE = new File(AmetysHomeHelper.getAmetysHome(), "config" + File.separator + "synchronizable-collections.xml"); 091 092 private Map<String, SynchronizableContentsCollection> _synchronizableCollections; 093 private long _lastUpdate; 094 095 private SynchronizeContentsCollectionModelExtensionPoint _syncCollectionModelEP; 096 private ContentTypeExtensionPoint _contentTypeEP; 097 private UserPopulationDAO _userPopulationDAO; 098 private UserDirectoryFactory _userDirectoryFactory; 099 private WorkflowProvider _workflowProvider; 100 private SynchronizingContentOperatorExtensionPoint _synchronizingContentOperatorEP; 101 102 private ServiceManager _smanager; 103 private Context _context; 104 105 106 @Override 107 public void initialize() throws Exception 108 { 109 _synchronizableCollections = new HashMap<>(); 110 _lastUpdate = 0; 111 } 112 113 @Override 114 public void contextualize(Context context) throws ContextException 115 { 116 _context = context; 117 } 118 119 @Override 120 public void service(ServiceManager smanager) throws ServiceException 121 { 122 _smanager = smanager; 123 _syncCollectionModelEP = (SynchronizeContentsCollectionModelExtensionPoint) smanager.lookup(SynchronizeContentsCollectionModelExtensionPoint.ROLE); 124 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 125 _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE); 126 _userDirectoryFactory = (UserDirectoryFactory) smanager.lookup(UserDirectoryFactory.ROLE); 127 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 128 _synchronizingContentOperatorEP = (SynchronizingContentOperatorExtensionPoint) smanager.lookup(SynchronizingContentOperatorExtensionPoint.ROLE); 129 } 130 131 /** 132 * Gets a synchronizable contents collection to JSON format 133 * @param collectionId The id of the synchronizable contents collection to get 134 * @return An object representing a {@link SynchronizableContentsCollection} 135 */ 136 @Callable 137 public Map<String, Object> getSynchronizableContentsCollectionAsJson(String collectionId) 138 { 139 return getSynchronizableContentsCollectionAsJson(getSynchronizableContentsCollection(collectionId)); 140 } 141 142 /** 143 * Gets a synchronizable contents collection to JSON format 144 * @param collection The synchronizable contents collection to get 145 * @return An object representing a {@link SynchronizableContentsCollection} 146 */ 147 public Map<String, Object> getSynchronizableContentsCollectionAsJson(SynchronizableContentsCollection collection) 148 { 149 Map<String, Object> result = new LinkedHashMap<>(); 150 result.put("id", collection.getId()); 151 result.put("label", collection.getLabel()); 152 153 String cTypeId = collection.getContentType(); 154 result.put("contentTypeId", cTypeId); 155 result.put("contentType", _contentTypeEP.getExtension(cTypeId).getLabel()); 156 157 String modelId = collection.getSynchronizeCollectionModelId(); 158 result.put("modelId", modelId); 159 SynchronizableContentsCollectionModel model = _syncCollectionModelEP.getExtension(modelId); 160 result.put("model", model.getLabel()); 161 162 return result; 163 } 164 165 /** 166 * Get the synchronizable contents collections 167 * @return the synchronizable contents collections 168 */ 169 public List<SynchronizableContentsCollection> getSynchronizableContentsCollections() 170 { 171 _readFile(false); 172 return new ArrayList<>(_synchronizableCollections.values()); 173 } 174 175 /** 176 * Get a synchronizable contents collection by its id 177 * @param collectionId The id of collection 178 * @return the synchronizable contents collection or <code>null</code> if not found 179 */ 180 public SynchronizableContentsCollection getSynchronizableContentsCollection(String collectionId) 181 { 182 _readFile(false); 183 return _synchronizableCollections.get(collectionId); 184 } 185 186 private void _readFile(boolean forceRead) 187 { 188 try 189 { 190 if (!__CONFIGURATION_FILE.exists()) 191 { 192 _createFile(__CONFIGURATION_FILE); 193 } 194 else if (forceRead || __CONFIGURATION_FILE.lastModified() > _lastUpdate) 195 { 196 _lastUpdate = new Date().getTime(); 197 _synchronizableCollections = new LinkedHashMap<>(); 198 199 Configuration cfg = new DefaultConfigurationBuilder().buildFromFile(__CONFIGURATION_FILE); 200 for (Configuration collectionConfig : cfg.getChildren("collection")) 201 { 202 SynchronizableContentsCollection syncCollection = _createSynchronizableCollection(collectionConfig); 203 _synchronizableCollections.put(syncCollection.getId(), syncCollection); 204 } 205 } 206 } 207 catch (Exception e) 208 { 209 getLogger().error("Failed to retrieve synchronizable contents collections from the configuration file " + __CONFIGURATION_FILE, e); 210 } 211 } 212 213 private void _createFile(File file) throws IOException, TransformerConfigurationException, SAXException 214 { 215 file.createNewFile(); 216 try (OutputStream os = new FileOutputStream(file)) 217 { 218 TransformerHandler th = _getTransformerHandler(os); 219 220 th.startDocument(); 221 XMLUtils.createElement(th, "collections"); 222 th.endDocument(); 223 } 224 } 225 226 private SynchronizableContentsCollection _createSynchronizableCollection(Configuration collectionConfig) throws ConfigurationException 227 { 228 String modelId = collectionConfig.getChild("model").getAttribute("id"); 229 230 if (_syncCollectionModelEP.hasExtension(modelId)) 231 { 232 SynchronizableContentsCollectionModel model = _syncCollectionModelEP.getExtension(modelId); 233 Class<SynchronizableContentsCollection> synchronizableCollectionClass = model.getSynchronizableCollectionClass(); 234 235 SynchronizableContentsCollection synchronizableCollection = null; 236 try 237 { 238 synchronizableCollection = synchronizableCollectionClass.newInstance(); 239 } 240 catch (InstantiationException | IllegalAccessException e) 241 { 242 throw new IllegalArgumentException("Cannot instanciate the class " + synchronizableCollectionClass.getCanonicalName() + ". Check that there is a public constructor with no arguments."); 243 } 244 245 Logger logger = LoggerFactory.getLogger(synchronizableCollectionClass); 246 try 247 { 248 if (synchronizableCollection instanceof LogEnabled) 249 { 250 ((LogEnabled) synchronizableCollection).setLogger(logger); 251 } 252 253 LifecycleHelper.setupComponent(synchronizableCollection, new SLF4JLoggerAdapter(logger), _context, _smanager, collectionConfig); 254 } 255 catch (Exception e) 256 { 257 throw new ConfigurationException("The model id '" + modelId + "' is not a valid", e); 258 } 259 260 return synchronizableCollection; 261 } 262 263 throw new ConfigurationException("The model id '" + modelId + "' is not a valid model for collection '" + collectionConfig.getChild("id") + "'", collectionConfig); 264 } 265 266 /** 267 * Gets the configuration for creating/editing a collection of synchronizable contents. 268 * @return A map containing information about what is needed to create/edit a collection of synchronizable contents 269 * @throws Exception If an error occurs. 270 */ 271 @Callable 272 public Map<String, Object> getEditionConfiguration() throws Exception 273 { 274 Map<String, Object> result = new HashMap<>(); 275 276 // MODELS 277 List<Object> collectionModels = new ArrayList<>(); 278 for (String modelId : _syncCollectionModelEP.getExtensionsIds()) 279 { 280 SynchronizableContentsCollectionModel model = _syncCollectionModelEP.getExtension(modelId); 281 Map<String, Object> modelMap = new HashMap<>(); 282 modelMap.put("id", modelId); 283 modelMap.put("label", model.getLabel()); 284 modelMap.put("description", model.getDescription()); 285 286 Map<String, Object> params = new LinkedHashMap<>(); 287 for (String paramId : model.getParameters().keySet()) 288 { 289 // prefix in case of two parameters from two different models have the same id which can lead to some errors in client-side 290 params.put(modelId + "$" + paramId, ParameterHelper.toJSON(model.getParameters().get(paramId))); 291 } 292 modelMap.put("parameters", params); 293 294 collectionModels.add(modelMap); 295 } 296 result.put("models", collectionModels); 297 298 // CONTENT TYPES 299 List<Object> contentTypes = new ArrayList<>(); 300 for (String contentTypeId : _contentTypeEP.getExtensionsIds()) 301 { 302 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 303 if (_isValidContentType(contentType)) 304 { 305 Map<String, Object> contentTypeMap = new HashMap<>(); 306 contentTypeMap.put("value", contentType.getId()); 307 contentTypeMap.put("label", contentType.getLabel()); 308 309 contentTypes.add(contentTypeMap); 310 } 311 } 312 result.put("contentTypes", contentTypes); 313 314 // WORKFLOWS 315 List<Object> workflows = new ArrayList<>(); 316 String[] workflowNames = _workflowProvider.getAmetysObjectWorkflow().getWorkflowNames(); 317 for (String workflowName : workflowNames) 318 { 319 Map<String, Object> workflowMap = new HashMap<>(); 320 workflowMap.put("value", workflowName); 321 workflowMap.put("label", new I18nizableText("application", "WORKFLOW_" + workflowName)); 322 workflows.add(workflowMap); 323 } 324 result.put("workflows", workflows); 325 326 // SYNCHRONIZING CONTENT OPERATORS 327 List<Object> operators = new ArrayList<>(); 328 for (String operatorId : _synchronizingContentOperatorEP.getExtensionsIds()) 329 { 330 Map<String, Object> operatorMap = new HashMap<>(); 331 operatorMap.put("value", operatorId); 332 operatorMap.put("label", _synchronizingContentOperatorEP.getExtension(operatorId).getLabel()); 333 operators.add(operatorMap); 334 } 335 result.put("contentOperators", operators); 336 result.put("defaultContentOperator", DefaultSynchronizingContentOperator.class.getName()); 337 338 return result; 339 } 340 341 private boolean _isValidContentType (ContentType cType) 342 { 343 return !cType.isSimple() && !cType.isAbstract() && !cType.isMixin(); 344 } 345 346 /** 347 * Gets the values of the parameters of the given collection 348 * @param collectionId The id of the collection 349 * @return The values of the parameters 350 */ 351 @Callable 352 public Map<String, Object> getCollectionParameterValues(String collectionId) 353 { 354 Map<String, Object> result = new LinkedHashMap<>(); 355 356 SynchronizableContentsCollection collection = getSynchronizableContentsCollection(collectionId); 357 if (collection == null) 358 { 359 getLogger().error("The collection of id '{}' does not exist.", collectionId); 360 result.put("error", "unknown"); 361 return result; 362 } 363 364 result.put("id", collectionId); 365 result.put("label", collection.getLabel()); 366 String modelId = collection.getSynchronizeCollectionModelId(); 367 result.put("modelId", modelId); 368 369 result.put("contentType", collection.getContentType()); 370 result.put("contentPrefix", collection.getContentPrefix()); 371 result.put("restrictedField", collection.getRestrictedField()); 372 result.put("removalSync", collection.removalSync()); 373 result.put("validateAfterImport", collection.validateAfterImport()); 374 375 result.put("workflowName", collection.getWorkflowName()); 376 result.put("initialActionId", collection.getInitialActionId()); 377 result.put("validateActionId", collection.getValidateActionId()); 378 379 result.put("contentOperator", collection.getSynchronizingContentOperator()); 380 result.put("reportMails", collection.getReportMails()); 381 382 Map<String, Object> values = collection.getParameterValues(); 383 for (String key : values.keySet()) 384 { 385 result.put(modelId + "$" + key, values.get(key)); 386 } 387 388 return result; 389 } 390 391 /** 392 * Gets the supported user directories (i.e. user directories based on a datasource) of the population in a json map 393 * @param populationId The id of the user population 394 * @return the supported user directories (i.e. user directories based on a datasource) of the population in a json map 395 */ 396 @Callable 397 public List<Map<String, Object>> getSupportedUserDirectories(String populationId) 398 { 399 List<Map<String, Object>> result = new ArrayList<>(); 400 401 UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId); 402 List<UserDirectory> userDirectories = userPopulation.getUserDirectories(); 403 for (String udId : _getDatasourceBasedUserDirectories(userDirectories)) 404 { 405 UserDirectory userDirectory = userPopulation.getUserDirectory(udId); 406 String udModelId = userDirectory.getUserDirectoryModelId(); 407 UserDirectoryModel udModel = _userDirectoryFactory.getExtension(udModelId); 408 Map<String, Object> udMap = new HashMap<>(); 409 410 udMap.put("id", udId); 411 udMap.put("modelLabel", udModel.getLabel()); 412 413 if (userDirectory instanceof JdbcUserDirectory) 414 { 415 udMap.put("type", "SQL"); 416 } 417 else if (userDirectories instanceof LdapUserDirectory) 418 { 419 udMap.put("type", "LDAP"); 420 } 421 422 result.add(udMap); 423 } 424 425 return result; 426 } 427 428 private List<String> _getDatasourceBasedUserDirectories(List<UserDirectory> userDirectories) 429 { 430 List<String> ids = new ArrayList<>(); 431 for (UserDirectory userDirectory : userDirectories) 432 { 433 if (userDirectory instanceof JdbcUserDirectory || userDirectory instanceof LdapUserDirectory) 434 { 435 ids.add(userDirectory.getId()); 436 } 437 } 438 439 return ids; 440 } 441 442 private boolean _writeFile() 443 { 444 File backup = _createBackup(); 445 boolean errorOccured = false; 446 447 // Do writing 448 try (OutputStream os = new FileOutputStream(__CONFIGURATION_FILE)) 449 { 450 TransformerHandler th = _getTransformerHandler(os); 451 452 // sax the config 453 try 454 { 455 th.startDocument(); 456 XMLUtils.startElement(th, "collections"); 457 458 _toSAX(th); 459 XMLUtils.endElement(th, "collections"); 460 th.endDocument(); 461 } 462 catch (Exception e) 463 { 464 getLogger().error("Error when saxing the collections", e); 465 errorOccured = true; 466 } 467 } 468 catch (IOException | TransformerConfigurationException | TransformerFactoryConfigurationError e) 469 { 470 if (getLogger().isErrorEnabled()) 471 { 472 getLogger().error("Error when trying to modify the group directories with the configuration file " + __CONFIGURATION_FILE, e); 473 } 474 } 475 476 _restoreBackup(backup, errorOccured); 477 478 return errorOccured; 479 } 480 481 private File _createBackup() 482 { 483 File backup = new File(__CONFIGURATION_FILE.getPath() + ".tmp"); 484 485 // Create a backup file 486 try 487 { 488 Files.copy(__CONFIGURATION_FILE.toPath(), backup.toPath()); 489 } 490 catch (IOException e) 491 { 492 getLogger().error("Error when creating backup '{}' file", e); 493 } 494 495 return backup; 496 } 497 498 private void _restoreBackup(File backup, boolean errorOccured) 499 { 500 // Restore the file if an error previously occured 501 try 502 { 503 if (errorOccured) 504 { 505 // An error occured, restore the original 506 Files.copy(backup.toPath(), __CONFIGURATION_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING); 507 // Force to reread the file 508 _readFile(true); 509 } 510 Files.deleteIfExists(backup.toPath()); 511 } 512 catch (IOException e) 513 { 514 if (getLogger().isErrorEnabled()) 515 { 516 getLogger().error("Error when restoring backup '" + __CONFIGURATION_FILE + "' file", e); 517 } 518 } 519 } 520 521 /** 522 * Add a new {@link SynchronizableContentsCollection} 523 * @param values The parameters' values 524 * @return The id of new created collection or null in case of error 525 * @throws ProcessingException if creation failed 526 */ 527 @Callable 528 public String addCollection (Map<String, Object> values) throws ProcessingException 529 { 530 _readFile(false); 531 532 String id = _generateUniqueId((String) values.get("label")); 533 534 try 535 { 536 _addCollection(id, values); 537 return id; 538 } 539 catch (Exception e) 540 { 541 throw new ProcessingException("Failed to add new collection'" + id + "'", e); 542 } 543 } 544 545 /** 546 * Edit a {@link SynchronizableContentsCollection} 547 * @param id The id of collection to edit 548 * @param values The parameters' values 549 * @return The id of new created collection or null in case of error 550 * @throws ProcessingException if edition failed 551 */ 552 @Callable 553 public Map<String, Object> editCollection (String id, Map<String, Object> values) throws ProcessingException 554 { 555 Map<String, Object> result = new LinkedHashMap<>(); 556 557 SynchronizableContentsCollection collection = _synchronizableCollections.get(id); 558 if (collection == null) 559 { 560 getLogger().error("The collection with id '{}' does not exist, it cannot be edited.", id); 561 result.put("error", "unknown"); 562 return result; 563 } 564 else 565 { 566 _synchronizableCollections.remove(id); 567 } 568 569 try 570 { 571 _addCollection(id, values); 572 result.put("id", id); 573 return result; 574 } 575 catch (Exception e) 576 { 577 throw new ProcessingException("Failed to edit collection of id '" + id + "'", e); 578 } 579 } 580 581 private boolean _addCollection(String id, Map<String, Object> values) throws FileNotFoundException, IOException, TransformerConfigurationException, SAXException 582 { 583 File backup = _createBackup(); 584 boolean success = false; 585 586 // Do writing 587 try (OutputStream os = new FileOutputStream(__CONFIGURATION_FILE)) 588 { 589 TransformerHandler th = _getTransformerHandler(os); 590 591 // sax the config 592 th.startDocument(); 593 XMLUtils.startElement(th, "collections"); 594 595 // SAX already existing collections 596 _toSAX(th); 597 598 // SAX the new collection 599 _saxCollection(th, id, values); 600 601 XMLUtils.endElement(th, "collections"); 602 th.endDocument(); 603 604 success = true; 605 } 606 607 _restoreBackup(backup, !success); 608 609 _readFile(false); 610 611 return success; 612 } 613 614 private TransformerHandler _getTransformerHandler(OutputStream os) throws TransformerConfigurationException 615 { 616 // create a transformer for saving sax into a file 617 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 618 619 StreamResult result = new StreamResult(os); 620 th.setResult(result); 621 622 // create the format of result 623 Properties format = new Properties(); 624 format.put(OutputKeys.METHOD, "xml"); 625 format.put(OutputKeys.INDENT, "yes"); 626 format.put(OutputKeys.ENCODING, "UTF-8"); 627 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 628 th.getTransformer().setOutputProperties(format); 629 630 return th; 631 } 632 633 /** 634 * Removes the given collection 635 * @param id The id of the collection to remove 636 * @return A map containing the id of the removed collection, or an error 637 */ 638 @Callable 639 public Map<String, Object> removeCollection(String id) 640 { 641 Map<String, Object> result = new LinkedHashMap<>(); 642 643 _readFile(false); 644 if (_synchronizableCollections.remove(id) == null) 645 { 646 getLogger().error("The synchronizable collection with id '{}' does not exist, it cannot be removed.", id); 647 result.put("error", "unknown"); 648 return result; 649 } 650 651 if (_writeFile()) 652 { 653 return null; 654 } 655 656 result.put("id", id); 657 return result; 658 } 659 660 private String _generateUniqueId(String label) 661 { 662 // Id generated from name lowercased, trimmed, and spaces and underscores replaced by dashes 663 String value = label.toLowerCase().trim().replaceAll("[\\W_]", "-").replaceAll("-+", "-").replaceAll("^-", ""); 664 int i = 2; 665 String suffixedValue = value; 666 while (_synchronizableCollections.get(suffixedValue) != null) 667 { 668 suffixedValue = value + i; 669 i++; 670 } 671 672 return suffixedValue; 673 } 674 675 private void _toSAX(TransformerHandler handler) throws SAXException 676 { 677 for (SynchronizableContentsCollection collection : _synchronizableCollections.values()) 678 { 679 _saxCollection(handler, collection); 680 } 681 } 682 683 private void _saxCollection(ContentHandler handler, String id, Map<String, Object> parameters) throws SAXException 684 { 685 AttributesImpl atts = new AttributesImpl(); 686 atts.addCDATAAttribute("id", id); 687 688 XMLUtils.startElement(handler, "collection", atts); 689 690 String label = (String) parameters.get("label"); 691 if (label != null) 692 { 693 new I18nizableText(label).toSAX(handler, "label"); 694 } 695 696 _saxNonNullValue(handler, "contentType", parameters.get("contentType")); 697 _saxNonNullValue(handler, "contentPrefix", parameters.get("contentPrefix")); 698 _saxNonNullValue(handler, "restrictedField", parameters.get("restrictedField")); 699 _saxNonNullValue(handler, "removalSync", parameters.get("removalSync")); 700 701 _saxNonNullValue(handler, "workflowName", parameters.get("workflowName")); 702 _saxNonNullValue(handler, "initialActionId", parameters.get("initialActionId")); 703 _saxNonNullValue(handler, "validateActionId", parameters.get("validateActionId")); 704 _saxNonNullValue(handler, "validateAfterImport", parameters.get("validateAfterImport")); 705 706 _saxNonNullValue(handler, "reportMails", parameters.get("reportMails")); 707 _saxNonNullValue(handler, "contentOperator", parameters.get("contentOperator")); 708 709 parameters.remove("id"); 710 parameters.remove("label"); 711 parameters.remove("contentType"); 712 parameters.remove("removalSync"); 713 parameters.remove("workflowName"); 714 parameters.remove("initialActionId"); 715 parameters.remove("validateActionId"); 716 parameters.remove("contentPrefix"); 717 parameters.remove("validateAfterImport"); 718 parameters.remove("reportMails"); 719 parameters.remove("contentOperator"); 720 parameters.remove("idField"); 721 parameters.remove("restrictedField"); 722 723 String modelId = (String) parameters.get("modelId"); 724 parameters.remove("modelId"); 725 726 _saxModel(handler, modelId, parameters, true); 727 728 XMLUtils.endElement(handler, "collection"); 729 } 730 731 private void _saxCollection(ContentHandler handler, SynchronizableContentsCollection collection) throws SAXException 732 { 733 AttributesImpl atts = new AttributesImpl(); 734 atts.addCDATAAttribute("id", collection.getId()); 735 XMLUtils.startElement(handler, "collection", atts); 736 737 collection.getLabel().toSAX(handler, "label"); 738 739 _saxNonNullValue(handler, "contentType", collection.getContentType()); 740 _saxNonNullValue(handler, "contentPrefix", collection.getContentPrefix()); 741 _saxNonNullValue(handler, "restrictedField", collection.getRestrictedField()); 742 743 _saxNonNullValue(handler, "workflowName", collection.getWorkflowName()); 744 _saxNonNullValue(handler, "initialActionId", collection.getInitialActionId()); 745 _saxNonNullValue(handler, "validateActionId", collection.getValidateActionId()); 746 _saxNonNullValue(handler, "validateAfterImport", collection.validateAfterImport()); 747 748 _saxNonNullValue(handler, "reportMails", collection.getReportMails()); 749 _saxNonNullValue(handler, "contentOperator", collection.getSynchronizingContentOperator()); 750 _saxNonNullValue(handler, "removalSync", collection.removalSync()); 751 752 _saxModel(handler, collection.getSynchronizeCollectionModelId(), collection.getParameterValues(), false); 753 754 XMLUtils.endElement(handler, "collection"); 755 } 756 757 private void _saxNonNullValue (ContentHandler handler, String tagName, Object value) throws SAXException 758 { 759 if (value != null) 760 { 761 XMLUtils.createElement(handler, tagName, ParameterHelper.valueToString(value)); 762 } 763 } 764 765 private void _saxModel(ContentHandler handler, String modelId, Map<String, Object> paramValues, boolean withPrefix) throws SAXException 766 { 767 AttributesImpl atts = new AttributesImpl(); 768 atts.addCDATAAttribute("id", modelId); 769 XMLUtils.startElement(handler, "model", atts); 770 771 for (String paramName : paramValues.keySet()) 772 { 773 if (!withPrefix || paramName.startsWith(modelId)) 774 { 775 Object value = paramValues.get(paramName); 776 if (value != null) 777 { 778 atts.clear(); 779 atts.addCDATAAttribute("name", withPrefix ? paramName.substring(modelId.length() + 1) : paramName); 780 XMLUtils.createElement(handler, "param", atts, ParameterHelper.valueToString(value)); 781 } 782 } 783 } 784 785 XMLUtils.endElement(handler, "model"); 786 } 787 788 @Override 789 public void dispose() 790 { 791 _synchronizableCollections.clear(); 792 } 793}