001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.replication.plugin; 018 019import static org.opends.messages.ReplicationMessages.*; 020import static org.opends.server.replication.plugin.ReplicationRepairRequestControl.*; 021import static org.opends.server.util.ServerConstants.*; 022import static org.opends.server.util.StaticUtils.*; 023 024import java.util.ArrayList; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.BlockingQueue; 031import java.util.concurrent.ConcurrentHashMap; 032import java.util.concurrent.LinkedBlockingQueue; 033import java.util.concurrent.atomic.AtomicReference; 034import java.util.concurrent.locks.ReentrantLock; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.config.server.ConfigChangeResult; 039import org.forgerock.opendj.config.server.ConfigException; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.opends.server.admin.server.ConfigurationAddListener; 042import org.opends.server.admin.server.ConfigurationChangeListener; 043import org.opends.server.admin.server.ConfigurationDeleteListener; 044import org.opends.server.admin.std.server.ReplicationDomainCfg; 045import org.opends.server.admin.std.server.ReplicationSynchronizationProviderCfg; 046import org.opends.server.api.Backend; 047import org.opends.server.api.BackupTaskListener; 048import org.opends.server.api.ExportTaskListener; 049import org.opends.server.api.ImportTaskListener; 050import org.opends.server.api.RestoreTaskListener; 051import org.opends.server.api.SynchronizationProvider; 052import org.opends.server.core.DirectoryServer; 053import org.opends.server.replication.service.DSRSShutdownSync; 054import org.opends.server.types.BackupConfig; 055import org.opends.server.types.Control; 056import org.forgerock.opendj.ldap.DN; 057import org.opends.server.types.DirectoryException; 058import org.opends.server.types.Entry; 059import org.opends.server.types.LDIFExportConfig; 060import org.opends.server.types.LDIFImportConfig; 061import org.opends.server.types.Modification; 062import org.opends.server.types.Operation; 063import org.opends.server.types.RestoreConfig; 064import org.opends.server.types.SynchronizationProviderResult; 065import org.opends.server.types.operation.PluginOperation; 066import org.opends.server.types.operation.PostOperationAddOperation; 067import org.opends.server.types.operation.PostOperationDeleteOperation; 068import org.opends.server.types.operation.PostOperationModifyDNOperation; 069import org.opends.server.types.operation.PostOperationModifyOperation; 070import org.opends.server.types.operation.PostOperationOperation; 071import org.opends.server.types.operation.PreOperationAddOperation; 072import org.opends.server.types.operation.PreOperationDeleteOperation; 073import org.opends.server.types.operation.PreOperationModifyDNOperation; 074import org.opends.server.types.operation.PreOperationModifyOperation; 075import org.opends.server.util.Platform; 076 077/** 078 * This class is used to load the Replication code inside the JVM 079 * and to trigger initialization of the replication. 080 * 081 * It also extends the SynchronizationProvider class in order to have some 082 * replication code running during the operation process 083 * as pre-op, conflictResolution, and post-op. 084 */ 085public class MultimasterReplication 086 extends SynchronizationProvider<ReplicationSynchronizationProviderCfg> 087 implements ConfigurationAddListener<ReplicationDomainCfg>, 088 ConfigurationDeleteListener<ReplicationDomainCfg>, 089 ConfigurationChangeListener 090 <ReplicationSynchronizationProviderCfg>, 091 BackupTaskListener, RestoreTaskListener, ImportTaskListener, 092 ExportTaskListener 093{ 094 095 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 096 097 private ReplicationServerListener replicationServerListener; 098 private static final Map<DN, LDAPReplicationDomain> domains = new ConcurrentHashMap<>(4); 099 private static final DSRSShutdownSync dsrsShutdownSync = new DSRSShutdownSync(); 100 /** The queue of received update messages, to be treated by the ReplayThread threads. */ 101 private static final BlockingQueue<UpdateToReplay> updateToReplayQueue = new LinkedBlockingQueue<>(10000); 102 /** The list of ReplayThread threads. */ 103 private static final List<ReplayThread> replayThreads = new ArrayList<>(); 104 /** The configurable number of replay threads. */ 105 private static int replayThreadNumber = 10; 106 107 /** Enum that symbolizes the state of the multimaster replication. */ 108 private static enum State 109 { 110 STARTING, RUNNING, STOPPING 111 } 112 113 private static final AtomicReference<State> state = new AtomicReference<>(State.STARTING); 114 115 /** The configurable connection/handshake timeout. */ 116 private static volatile int connectionTimeoutMS = 5000; 117 118 /** 119 * Finds the domain for a given DN. 120 * 121 * @param dn The DN for which the domain must be returned. 122 * @param pluginOp An optional operation for which the check is done. 123 * Can be null is the request has no associated operation. 124 * @return The domain for this DN. 125 */ 126 public static LDAPReplicationDomain findDomain(DN dn, PluginOperation pluginOp) 127 { 128 /* 129 * Don't run the special replication code on Operation that are 130 * specifically marked as don't synchronize. 131 */ 132 if (pluginOp instanceof Operation) 133 { 134 final Operation op = (Operation) pluginOp; 135 if (op.dontSynchronize()) 136 { 137 return null; 138 } 139 140 /* 141 * Check if the provided operation is a repair operation and set the 142 * synchronization flags if necessary. 143 * The repair operations are tagged as synchronization operations so 144 * that the core server let the operation modify the entryuuid and 145 * ds-sync-hist attributes. 146 * They are also tagged as dontSynchronize so that the replication code 147 * running later do not generate CSN, solve conflicts and forward the 148 * operation to the replication server. 149 */ 150 for (Iterator<Control> it = op.getRequestControls().iterator(); it.hasNext();) 151 { 152 Control c = it.next(); 153 if (OID_REPLICATION_REPAIR_CONTROL.equals(c.getOID())) 154 { 155 op.setSynchronizationOperation(true); 156 op.setDontSynchronize(true); 157 /* 158 remove this control from the list of controls since it has now been 159 processed and the local backend will fail if it finds a control that 160 it does not know about and that is marked as critical. 161 */ 162 it.remove(); 163 return null; 164 } 165 } 166 } 167 168 169 LDAPReplicationDomain domain = null; 170 DN temp = dn; 171 while (domain == null && temp != null) 172 { 173 domain = domains.get(temp); 174 temp = DirectoryServer.getParentDNInSuffix(temp); 175 } 176 177 return domain; 178 } 179 180 /** 181 * Creates a new domain from its configEntry, do the 182 * necessary initialization and starts it so that it is 183 * fully operational when this method returns. 184 * @param configuration The entry with the configuration of this domain. 185 * @return The domain created. 186 * @throws ConfigException When the configuration is not valid. 187 */ 188 public static LDAPReplicationDomain createNewDomain( 189 ReplicationDomainCfg configuration) 190 throws ConfigException 191 { 192 try 193 { 194 final LDAPReplicationDomain domain = new LDAPReplicationDomain( 195 configuration, updateToReplayQueue, dsrsShutdownSync); 196 if (domains.isEmpty()) 197 { 198 // Create the threads that will process incoming update messages 199 createReplayThreads(); 200 } 201 202 domains.put(domain.getBaseDN(), domain); 203 return domain; 204 } 205 catch (ConfigException e) 206 { 207 logger.error(ERR_COULD_NOT_START_REPLICATION, configuration.dn(), 208 e.getLocalizedMessage() + " " + stackTraceToSingleLineString(e)); 209 } 210 return null; 211 } 212 213 /** 214 * Creates a new domain from its configEntry, do the necessary initialization 215 * and starts it so that it is fully operational when this method returns. It 216 * is only used for tests so far. 217 * 218 * @param configuration The entry with the configuration of this domain. 219 * @param queue The BlockingQueue that this domain will use. 220 * 221 * @return The domain created. 222 * 223 * @throws ConfigException When the configuration is not valid. 224 */ 225 static LDAPReplicationDomain createNewDomain( 226 ReplicationDomainCfg configuration, 227 BlockingQueue<UpdateToReplay> queue) 228 throws ConfigException 229 { 230 final LDAPReplicationDomain domain = 231 new LDAPReplicationDomain(configuration, queue, dsrsShutdownSync); 232 domains.put(domain.getBaseDN(), domain); 233 return domain; 234 } 235 236 /** 237 * Deletes a domain. 238 * @param dn : the base DN of the domain to delete. 239 */ 240 public static void deleteDomain(DN dn) 241 { 242 LDAPReplicationDomain domain = domains.remove(dn); 243 if (domain != null) 244 { 245 domain.delete(); 246 } 247 248 // No replay threads running if no replication need 249 if (domains.isEmpty()) { 250 stopReplayThreads(); 251 } 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public void initializeSynchronizationProvider( 257 ReplicationSynchronizationProviderCfg cfg) throws ConfigException 258 { 259 domains.clear(); 260 replicationServerListener = new ReplicationServerListener(cfg, dsrsShutdownSync); 261 262 // Register as an add and delete listener with the root configuration so we 263 // can be notified if Multimaster domain entries are added or removed. 264 cfg.addReplicationDomainAddListener(this); 265 cfg.addReplicationDomainDeleteListener(this); 266 267 // Register as a root configuration listener so that we can be notified if 268 // number of replay threads is changed and apply changes. 269 cfg.addReplicationChangeListener(this); 270 271 replayThreadNumber = getNumberOfReplayThreadsOrDefault(cfg); 272 connectionTimeoutMS = (int) Math.min(cfg.getConnectionTimeout(), Integer.MAX_VALUE); 273 274 // Create the list of domains that are already defined. 275 for (String name : cfg.listReplicationDomains()) 276 { 277 createNewDomain(cfg.getReplicationDomain(name)); 278 } 279 280 // If any schema changes were made with the server offline, then handle them now. 281 List<Modification> offlineSchemaChanges = 282 DirectoryServer.getOfflineSchemaChanges(); 283 if (offlineSchemaChanges != null && !offlineSchemaChanges.isEmpty()) 284 { 285 processSchemaChange(offlineSchemaChanges); 286 } 287 288 DirectoryServer.registerBackupTaskListener(this); 289 DirectoryServer.registerRestoreTaskListener(this); 290 DirectoryServer.registerExportTaskListener(this); 291 DirectoryServer.registerImportTaskListener(this); 292 293 DirectoryServer.registerSupportedControl( 294 ReplicationRepairRequestControl.OID_REPLICATION_REPAIR_CONTROL); 295 } 296 297 private int getNumberOfReplayThreadsOrDefault(ReplicationSynchronizationProviderCfg cfg) 298 { 299 Integer value = cfg.getNumUpdateReplayThreads(); 300 return value == null ? Platform.computeNumberOfThreads(16, 2.0f) : value; 301 } 302 303 /** 304 * Create the threads that will wait for incoming update messages. 305 */ 306 private static synchronized void createReplayThreads() 307 { 308 replayThreads.clear(); 309 310 ReentrantLock switchQueueLock = new ReentrantLock(); 311 for (int i = 0; i < replayThreadNumber; i++) 312 { 313 ReplayThread replayThread = new ReplayThread(updateToReplayQueue, switchQueueLock); 314 replayThread.start(); 315 replayThreads.add(replayThread); 316 } 317 } 318 319 /** 320 * Stop the threads that are waiting for incoming update messages. 321 */ 322 private static synchronized void stopReplayThreads() 323 { 324 // stop the replay threads 325 for (ReplayThread replayThread : replayThreads) 326 { 327 replayThread.shutdown(); 328 } 329 330 for (ReplayThread replayThread : replayThreads) 331 { 332 try 333 { 334 replayThread.join(); 335 } 336 catch(InterruptedException e) 337 { 338 Thread.currentThread().interrupt(); 339 } 340 } 341 replayThreads.clear(); 342 } 343 344 /** {@inheritDoc} */ 345 @Override 346 public boolean isConfigurationAddAcceptable( 347 ReplicationDomainCfg configuration, List<LocalizableMessage> unacceptableReasons) 348 { 349 return LDAPReplicationDomain.isConfigurationAcceptable( 350 configuration, unacceptableReasons); 351 } 352 353 /** {@inheritDoc} */ 354 @Override 355 public ConfigChangeResult applyConfigurationAdd( 356 ReplicationDomainCfg configuration) 357 { 358 ConfigChangeResult ccr = new ConfigChangeResult(); 359 try 360 { 361 LDAPReplicationDomain rd = createNewDomain(configuration); 362 if (State.RUNNING.equals(state.get())) 363 { 364 rd.start(); 365 if (State.STOPPING.equals(state.get())) { 366 rd.shutdown(); 367 } 368 } 369 } catch (ConfigException e) 370 { 371 // we should never get to this point because the configEntry has 372 // already been validated in isConfigurationAddAcceptable() 373 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 374 } 375 return ccr; 376 } 377 378 /** {@inheritDoc} */ 379 @Override 380 public void doPostOperation(PostOperationAddOperation addOperation) 381 { 382 DN dn = addOperation.getEntryDN(); 383 genericPostOperation(addOperation, dn); 384 } 385 386 387 /** {@inheritDoc} */ 388 @Override 389 public void doPostOperation(PostOperationDeleteOperation deleteOperation) 390 { 391 DN dn = deleteOperation.getEntryDN(); 392 genericPostOperation(deleteOperation, dn); 393 } 394 395 /** {@inheritDoc} */ 396 @Override 397 public void doPostOperation(PostOperationModifyDNOperation modifyDNOperation) 398 { 399 DN dn = modifyDNOperation.getEntryDN(); 400 genericPostOperation(modifyDNOperation, dn); 401 } 402 403 /** {@inheritDoc} */ 404 @Override 405 public void doPostOperation(PostOperationModifyOperation modifyOperation) 406 { 407 DN dn = modifyOperation.getEntryDN(); 408 genericPostOperation(modifyOperation, dn); 409 } 410 411 /** {@inheritDoc} */ 412 @Override 413 public SynchronizationProviderResult handleConflictResolution( 414 PreOperationModifyOperation modifyOperation) 415 { 416 LDAPReplicationDomain domain = findDomain(modifyOperation.getEntryDN(), modifyOperation); 417 if (domain != null) 418 { 419 return domain.handleConflictResolution(modifyOperation); 420 } 421 return new SynchronizationProviderResult.ContinueProcessing(); 422 } 423 424 /** {@inheritDoc} */ 425 @Override 426 public SynchronizationProviderResult handleConflictResolution( 427 PreOperationAddOperation addOperation) throws DirectoryException 428 { 429 LDAPReplicationDomain domain = findDomain(addOperation.getEntryDN(), addOperation); 430 if (domain != null) 431 { 432 return domain.handleConflictResolution(addOperation); 433 } 434 return new SynchronizationProviderResult.ContinueProcessing(); 435 } 436 437 /** {@inheritDoc} */ 438 @Override 439 public SynchronizationProviderResult handleConflictResolution( 440 PreOperationDeleteOperation deleteOperation) throws DirectoryException 441 { 442 LDAPReplicationDomain domain = findDomain(deleteOperation.getEntryDN(), deleteOperation); 443 if (domain != null) 444 { 445 return domain.handleConflictResolution(deleteOperation); 446 } 447 return new SynchronizationProviderResult.ContinueProcessing(); 448 } 449 450 /** {@inheritDoc} */ 451 @Override 452 public SynchronizationProviderResult handleConflictResolution( 453 PreOperationModifyDNOperation modifyDNOperation) throws DirectoryException 454 { 455 LDAPReplicationDomain domain = findDomain(modifyDNOperation.getEntryDN(), modifyDNOperation); 456 if (domain != null) 457 { 458 return domain.handleConflictResolution(modifyDNOperation); 459 } 460 return new SynchronizationProviderResult.ContinueProcessing(); 461 } 462 463 /** {@inheritDoc} */ 464 @Override 465 public SynchronizationProviderResult 466 doPreOperation(PreOperationModifyOperation modifyOperation) 467 { 468 DN operationDN = modifyOperation.getEntryDN(); 469 LDAPReplicationDomain domain = findDomain(operationDN, modifyOperation); 470 471 if (domain == null || !domain.solveConflict()) 472 { 473 return new SynchronizationProviderResult.ContinueProcessing(); 474 } 475 476 EntryHistorical historicalInformation = (EntryHistorical) 477 modifyOperation.getAttachment(EntryHistorical.HISTORICAL); 478 if (historicalInformation == null) 479 { 480 Entry entry = modifyOperation.getModifiedEntry(); 481 historicalInformation = EntryHistorical.newInstanceFromEntry(entry); 482 modifyOperation.setAttachment(EntryHistorical.HISTORICAL, 483 historicalInformation); 484 } 485 historicalInformation.setPurgeDelay(domain.getHistoricalPurgeDelay()); 486 historicalInformation.setHistoricalAttrToOperation(modifyOperation); 487 488 if (modifyOperation.getModifications().isEmpty()) 489 { 490 /* 491 * This operation becomes a no-op due to conflict resolution 492 * stop the processing and send an OK result 493 */ 494 return new SynchronizationProviderResult.StopProcessing( 495 ResultCode.SUCCESS, null); 496 } 497 498 return new SynchronizationProviderResult.ContinueProcessing(); 499 } 500 501 /** {@inheritDoc} */ 502 @Override 503 public SynchronizationProviderResult doPreOperation( 504 PreOperationDeleteOperation deleteOperation) throws DirectoryException 505 { 506 return new SynchronizationProviderResult.ContinueProcessing(); 507 } 508 509 /** {@inheritDoc} */ 510 @Override 511 public SynchronizationProviderResult doPreOperation( 512 PreOperationModifyDNOperation modifyDNOperation) 513 throws DirectoryException 514 { 515 DN operationDN = modifyDNOperation.getEntryDN(); 516 LDAPReplicationDomain domain = findDomain(operationDN, modifyDNOperation); 517 518 if (domain == null || !domain.solveConflict()) 519 { 520 return new SynchronizationProviderResult.ContinueProcessing(); 521 } 522 523 // The historical object is retrieved from the attachment created 524 // in the HandleConflictResolution phase. 525 EntryHistorical historicalInformation = (EntryHistorical) 526 modifyDNOperation.getAttachment(EntryHistorical.HISTORICAL); 527 if (historicalInformation == null) 528 { 529 // When no Historical attached, create once by loading from the entry 530 // and attach it to the operation 531 Entry entry = modifyDNOperation.getUpdatedEntry(); 532 historicalInformation = EntryHistorical.newInstanceFromEntry(entry); 533 modifyDNOperation.setAttachment(EntryHistorical.HISTORICAL, 534 historicalInformation); 535 } 536 historicalInformation.setPurgeDelay(domain.getHistoricalPurgeDelay()); 537 538 // Add to the operation the historical attribute : "dn:changeNumber:moddn" 539 historicalInformation.setHistoricalAttrToOperation(modifyDNOperation); 540 541 return new SynchronizationProviderResult.ContinueProcessing(); 542 } 543 544 /** {@inheritDoc} */ 545 @Override 546 public SynchronizationProviderResult doPreOperation( 547 PreOperationAddOperation addOperation) 548 { 549 // Check replication domain 550 LDAPReplicationDomain domain = 551 findDomain(addOperation.getEntryDN(), addOperation); 552 if (domain == null) 553 { 554 return new SynchronizationProviderResult.ContinueProcessing(); 555 } 556 557 // For LOCAL op only, generate CSN and attach Context 558 if (!addOperation.isSynchronizationOperation()) 559 { 560 domain.doPreOperation(addOperation); 561 } 562 563 // Add to the operation the historical attribute : "dn:changeNumber:add" 564 EntryHistorical.setHistoricalAttrToOperation(addOperation); 565 566 return new SynchronizationProviderResult.ContinueProcessing(); 567 } 568 569 /** {@inheritDoc} */ 570 @Override 571 public void finalizeSynchronizationProvider() 572 { 573 setState(State.STOPPING); 574 575 for (LDAPReplicationDomain domain : domains.values()) 576 { 577 domain.shutdown(); 578 } 579 domains.clear(); 580 581 stopReplayThreads(); 582 583 if (replicationServerListener != null) 584 { 585 replicationServerListener.shutdown(); 586 } 587 588 DirectoryServer.deregisterBackupTaskListener(this); 589 DirectoryServer.deregisterRestoreTaskListener(this); 590 DirectoryServer.deregisterExportTaskListener(this); 591 DirectoryServer.deregisterImportTaskListener(this); 592 } 593 594 /** 595 * This method is called whenever the server detects a modification 596 * of the schema done by directly modifying the backing files 597 * of the schema backend. 598 * Call the schema Domain if it exists. 599 * 600 * @param modifications The list of modifications that was 601 * applied to the schema. 602 * 603 */ 604 @Override 605 public void processSchemaChange(List<Modification> modifications) 606 { 607 LDAPReplicationDomain domain = findDomain(DirectoryServer.getSchemaDN(), null); 608 if (domain != null) 609 { 610 domain.synchronizeSchemaModifications(modifications); 611 } 612 } 613 614 /** {@inheritDoc} */ 615 @Override 616 public void processBackupBegin(Backend backend, BackupConfig config) 617 { 618 for (DN dn : backend.getBaseDNs()) 619 { 620 LDAPReplicationDomain domain = findDomain(dn, null); 621 if (domain != null) 622 { 623 domain.backupStart(); 624 } 625 } 626 } 627 628 /** {@inheritDoc} */ 629 @Override 630 public void processBackupEnd(Backend backend, BackupConfig config, 631 boolean successful) 632 { 633 for (DN dn : backend.getBaseDNs()) 634 { 635 LDAPReplicationDomain domain = findDomain(dn, null); 636 if (domain != null) 637 { 638 domain.backupEnd(); 639 } 640 } 641 } 642 643 /** {@inheritDoc} */ 644 @Override 645 public void processRestoreBegin(Backend backend, RestoreConfig config) 646 { 647 for (DN dn : backend.getBaseDNs()) 648 { 649 LDAPReplicationDomain domain = findDomain(dn, null); 650 if (domain != null) 651 { 652 domain.disable(); 653 } 654 } 655 } 656 657 /** {@inheritDoc} */ 658 @Override 659 public void processRestoreEnd(Backend backend, RestoreConfig config, 660 boolean successful) 661 { 662 for (DN dn : backend.getBaseDNs()) 663 { 664 LDAPReplicationDomain domain = findDomain(dn, null); 665 if (domain != null) 666 { 667 domain.enable(); 668 } 669 } 670 } 671 672 /** {@inheritDoc} */ 673 @Override 674 public void processImportBegin(Backend backend, LDIFImportConfig config) 675 { 676 for (DN dn : backend.getBaseDNs()) 677 { 678 LDAPReplicationDomain domain = findDomain(dn, null); 679 if (domain != null) 680 { 681 domain.disable(); 682 } 683 } 684 } 685 686 /** {@inheritDoc} */ 687 @Override 688 public void processImportEnd(Backend backend, LDIFImportConfig config, 689 boolean successful) 690 { 691 for (DN dn : backend.getBaseDNs()) 692 { 693 LDAPReplicationDomain domain = findDomain(dn, null); 694 if (domain != null) 695 { 696 domain.enable(); 697 } 698 } 699 } 700 701 /** {@inheritDoc} */ 702 @Override 703 public void processExportBegin(Backend backend, LDIFExportConfig config) 704 { 705 for (DN dn : backend.getBaseDNs()) 706 { 707 LDAPReplicationDomain domain = findDomain(dn, null); 708 if (domain != null) 709 { 710 domain.backupStart(); 711 } 712 } 713 } 714 715 /** {@inheritDoc} */ 716 @Override 717 public void processExportEnd(Backend backend, LDIFExportConfig config, 718 boolean successful) 719 { 720 for (DN dn : backend.getBaseDNs()) 721 { 722 LDAPReplicationDomain domain = findDomain(dn, null); 723 if (domain != null) 724 { 725 domain.backupEnd(); 726 } 727 } 728 } 729 730 /** {@inheritDoc} */ 731 @Override 732 public ConfigChangeResult applyConfigurationDelete( 733 ReplicationDomainCfg configuration) 734 { 735 deleteDomain(configuration.getBaseDN()); 736 737 return new ConfigChangeResult(); 738 } 739 740 /** {@inheritDoc} */ 741 @Override 742 public boolean isConfigurationDeleteAcceptable( 743 ReplicationDomainCfg configuration, List<LocalizableMessage> unacceptableReasons) 744 { 745 return true; 746 } 747 748 /** 749 * Generic code for all the postOperation entry point. 750 * 751 * @param operation The Operation for which the post-operation is called. 752 * @param dn The Dn for which the post-operation is called. 753 */ 754 private void genericPostOperation(PostOperationOperation operation, DN dn) 755 { 756 LDAPReplicationDomain domain = findDomain(dn, operation); 757 if (domain != null) { 758 domain.synchronize(operation); 759 } 760 } 761 762 /** 763 * Returns the replication server listener associated to that Multimaster 764 * Replication. 765 * @return the listener. 766 */ 767 public ReplicationServerListener getReplicationServerListener() 768 { 769 return replicationServerListener; 770 } 771 772 /** {@inheritDoc} */ 773 @Override 774 public boolean isConfigurationChangeAcceptable( 775 ReplicationSynchronizationProviderCfg configuration, 776 List<LocalizableMessage> unacceptableReasons) 777 { 778 return true; 779 } 780 781 @Override 782 public ConfigChangeResult applyConfigurationChange(ReplicationSynchronizationProviderCfg configuration) 783 { 784 785 // Stop threads then restart new number of threads 786 stopReplayThreads(); 787 replayThreadNumber = getNumberOfReplayThreadsOrDefault(configuration); 788 if (!domains.isEmpty()) 789 { 790 createReplayThreads(); 791 } 792 793 connectionTimeoutMS = (int) Math.min(configuration.getConnectionTimeout(), 794 Integer.MAX_VALUE); 795 796 return new ConfigChangeResult(); 797 } 798 799 /** {@inheritDoc} */ 800 @Override 801 public void completeSynchronizationProvider() 802 { 803 for (LDAPReplicationDomain domain : domains.values()) 804 { 805 domain.start(); 806 } 807 setState(State.RUNNING); 808 } 809 810 private void setState(State newState) 811 { 812 state.set(newState); 813 synchronized (state) 814 { 815 state.notifyAll(); 816 } 817 } 818 819 /** 820 * Gets the number of handled domain objects. 821 * @return The number of handled domain objects 822 */ 823 public static int getNumberOfDomains() 824 { 825 return domains.size(); 826 } 827 828 /** 829 * Gets the Set of domain baseDN which are disabled for the external changelog. 830 * 831 * @return The Set of domain baseDNs which are disabled for the external changelog. 832 * @throws DirectoryException 833 * if a problem occurs 834 */ 835 public static Set<DN> getExcludedChangelogDomains() throws DirectoryException 836 { 837 final Set<DN> disabledBaseDNs = new HashSet<>(domains.size() + 1); 838 disabledBaseDNs.add(DN.valueOf(DN_EXTERNAL_CHANGELOG_ROOT)); 839 for (LDAPReplicationDomain domain : domains.values()) 840 { 841 if (!domain.isECLEnabled()) 842 { 843 disabledBaseDNs.add(domain.getBaseDN()); 844 } 845 } 846 return disabledBaseDNs; 847 } 848 849 /** 850 * Returns whether the provided baseDN represents a replication domain enabled 851 * for the external changelog. 852 * 853 * @param baseDN 854 * the replication domain to check 855 * @return true if the provided baseDN is enabled for the external changelog, 856 * false if the provided baseDN is disabled for the external changelog 857 * or unknown to multimaster replication. 858 */ 859 public static boolean isECLEnabledDomain(DN baseDN) 860 { 861 waitForStartup(); 862 // if state is STOPPING, then we need to return from this method 863 final LDAPReplicationDomain domain = domains.get(baseDN); 864 return domain != null && domain.isECLEnabled(); 865 } 866 867 /** 868 * Returns whether the external change-log contains data from at least a domain. 869 * @return whether the external change-log contains data from at least a domain 870 */ 871 public static boolean isECLEnabled() 872 { 873 waitForStartup(); 874 for (LDAPReplicationDomain domain : domains.values()) 875 { 876 if (domain.isECLEnabled()) 877 { 878 return true; 879 } 880 } 881 return false; 882 } 883 884 private static void waitForStartup() 885 { 886 if (State.STARTING.equals(state.get())) 887 { 888 synchronized (state) 889 { 890 while (State.STARTING.equals(state.get())) 891 { 892 try 893 { 894 state.wait(); 895 } 896 catch (InterruptedException ignored) 897 { 898 // loop and check state again 899 } 900 } 901 } 902 } 903 } 904 905 /** 906 * Returns the connection timeout in milli-seconds. 907 * 908 * @return The connection timeout in milli-seconds. 909 */ 910 public static int getConnectionTimeoutMS() 911 { 912 return connectionTimeoutMS; 913 } 914 915}