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}