/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2007-2009 Sun Microsystems, Inc.
|
* Portions Copyright 2013-2014 ForgeRock AS.
|
*/
|
package org.opends.server.replication.plugin;
|
|
import java.util.*;
|
|
import org.opends.server.core.AddOperation;
|
import org.opends.server.core.DeleteOperation;
|
import org.opends.server.core.ModifyDNOperationBasis;
|
import org.opends.server.core.ModifyOperation;
|
import org.opends.server.replication.common.CSN;
|
import org.opends.server.replication.common.ServerState;
|
import org.opends.server.replication.protocol.*;
|
import org.opends.server.types.DN;
|
import org.opends.server.types.Operation;
|
|
/**
|
* This class is used to store the list of remote changes received
|
* from a replication server and that are either currently being replayed
|
* or that are waiting for being replayed.
|
*
|
* It is used to know when the ServerState must be updated and to compute
|
* the dependencies between operations.
|
*
|
* One of this object is instantiated for each ReplicationDomain.
|
*/
|
final class RemotePendingChanges
|
{
|
/**
|
* A map used to store the pending changes.
|
*/
|
private final SortedMap<CSN, PendingChange> pendingChanges =
|
new TreeMap<CSN, PendingChange>();
|
|
/**
|
* A sorted set containing the list of PendingChanges that have
|
* not been replayed correctly because they are dependent on
|
* another change to be completed.
|
*/
|
private final SortedSet<PendingChange> dependentChanges =
|
new TreeSet<PendingChange>();
|
|
/**
|
* The ServerState that will be updated when LDAPUpdateMsg are fully replayed.
|
*/
|
private final ServerState state;
|
|
/**
|
* Creates a new RemotePendingChanges using the provided ServerState.
|
*
|
* @param state The ServerState that will be updated when LDAPUpdateMsg
|
* have been fully replayed.
|
*/
|
public RemotePendingChanges(ServerState state)
|
{
|
this.state = state;
|
}
|
|
/**
|
* Returns the number of changes currently in this list.
|
*
|
* @return The number of changes currently in this list.
|
*/
|
public synchronized int getQueueSize()
|
{
|
return pendingChanges.size();
|
}
|
|
/**
|
* Add a new LDAPUpdateMsg that was received from the replication server
|
* to the pendingList.
|
*
|
* @param update The LDAPUpdateMsg that was received from the replication
|
* server and that will be added to the pending list.
|
* @return {@code false} if the update was already registered in the pending
|
* changes.
|
*/
|
public synchronized boolean putRemoteUpdate(LDAPUpdateMsg update)
|
{
|
CSN csn = update.getCSN();
|
return pendingChanges.put(csn,
|
new PendingChange(csn, null, update)) == null;
|
}
|
|
/**
|
* Mark an update message as committed.
|
*
|
* @param csn
|
* The CSN of the update message that must be set as committed.
|
*/
|
public synchronized void commit(CSN csn)
|
{
|
PendingChange curChange = pendingChanges.get(csn);
|
if (curChange == null)
|
{
|
throw new NoSuchElementException();
|
}
|
curChange.setCommitted(true);
|
|
CSN firstCSN = pendingChanges.firstKey();
|
PendingChange firstChange = pendingChanges.get(firstCSN);
|
|
while (firstChange != null && firstChange.isCommitted())
|
{
|
if (firstChange.getMsg().contributesToDomainState())
|
{
|
state.update(firstCSN);
|
}
|
pendingChanges.remove(firstCSN);
|
|
if (pendingChanges.isEmpty())
|
{
|
firstChange = null;
|
}
|
else
|
{
|
firstCSN = pendingChanges.firstKey();
|
firstChange = pendingChanges.get(firstCSN);
|
}
|
}
|
}
|
|
/**
|
* Get the first update in the list that have some dependencies cleared.
|
*
|
* @return The LDAPUpdateMsg to be handled.
|
*/
|
public synchronized LDAPUpdateMsg getNextUpdate()
|
{
|
/*
|
* Parse the list of Update with dependencies and check if the dependencies
|
* are now cleared until an Update without dependencies is found.
|
*/
|
for (PendingChange change : dependentChanges)
|
{
|
if (change.dependenciesIsCovered(state))
|
{
|
dependentChanges.remove(change);
|
return change.getLDAPUpdateMsg();
|
}
|
}
|
return null;
|
}
|
|
/**
|
* Mark the first pendingChange as dependent on the second PendingChange.
|
* @param dependentChange The PendingChange that depend on the second
|
* PendingChange.
|
* @param pendingChange The PendingChange on which the first PendingChange
|
* is dependent.
|
*/
|
private void addDependency(
|
PendingChange dependentChange, PendingChange pendingChange)
|
{
|
dependentChange.addDependency(pendingChange.getCSN());
|
dependentChanges.add(dependentChange);
|
}
|
|
/**
|
* Check if the given AddOperation has some dependencies on any
|
* currently running previous operation.
|
* Update the dependency list in the associated PendingChange if
|
* there are some dependencies.
|
* AddOperation depends on
|
*
|
* - DeleteOperation done on the same DN
|
* - ModifyDnOperation with the same target DN as the ADD DN
|
* - ModifyDnOperation with new DN equals to the ADD DN parent
|
* - AddOperation done on the parent DN of the ADD DN
|
*
|
* @param op The AddOperation to be checked.
|
*
|
* @return A boolean indicating if this operation has some dependencies.
|
*/
|
public synchronized boolean checkDependencies(AddOperation op)
|
{
|
boolean hasDependencies = false;
|
final DN targetDN = op.getEntryDN();
|
final CSN csn = OperationContext.getCSN(op);
|
final PendingChange change = pendingChanges.get(csn);
|
if (change == null)
|
{
|
return false;
|
}
|
|
for (PendingChange pendingChange : pendingChanges.values())
|
{
|
if (pendingChange.getCSN().isOlderThan(csn))
|
{
|
final LDAPUpdateMsg pendingMsg = pendingChange.getLDAPUpdateMsg();
|
if (pendingMsg != null)
|
{
|
if (pendingMsg instanceof DeleteMsg)
|
{
|
/*
|
* Check is the operation to be run is a deleteOperation on the
|
* same DN.
|
*/
|
if (pendingMsg.getDN().equals(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof AddMsg)
|
{
|
/*
|
* Check if the operation to be run is an addOperation on a
|
* parent of the current AddOperation.
|
*/
|
if (pendingMsg.getDN().isAncestorOf(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof ModifyDNMsg)
|
{
|
/*
|
* Check if the operation to be run is ModifyDnOperation with
|
* the same target DN as the ADD DN
|
* or a ModifyDnOperation with new DN equals to the ADD DN parent
|
*/
|
if (pendingMsg.getDN().equals(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
else
|
{
|
final ModifyDNMsg pendingModDn = (ModifyDNMsg) pendingMsg;
|
if (pendingModDn.newDNIsParent(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
}
|
}
|
}
|
else
|
{
|
// We reached an operation that is newer than the operation
|
// for which we are doing the dependency check so it is
|
// not possible to find another operation with some dependency.
|
// break the loop to avoid going through the potentially large
|
// list of pending changes.
|
break;
|
}
|
}
|
return hasDependencies;
|
}
|
|
/**
|
* Check if the given ModifyOperation has some dependencies on any
|
* currently running previous operation.
|
* Update the dependency list in the associated PendingChange if
|
* there are some dependencies.
|
*
|
* ModifyOperation depends on
|
* - AddOperation done on the same DN
|
*
|
* @param op The ModifyOperation to be checked.
|
*
|
* @return A boolean indicating if this operation has some dependencies.
|
*/
|
public synchronized boolean checkDependencies(ModifyOperation op)
|
{
|
boolean hasDependencies = false;
|
final DN targetDN = op.getEntryDN();
|
final CSN csn = OperationContext.getCSN(op);
|
final PendingChange change = pendingChanges.get(csn);
|
if (change == null)
|
{
|
return false;
|
}
|
|
for (PendingChange pendingChange : pendingChanges.values())
|
{
|
if (pendingChange.getCSN().isOlderThan(csn))
|
{
|
final LDAPUpdateMsg pendingMsg = pendingChange.getLDAPUpdateMsg();
|
if (pendingMsg instanceof AddMsg)
|
{
|
// Check if the operation to be run is an addOperation on a same DN.
|
if (pendingMsg.getDN().equals(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
}
|
else
|
{
|
// We reached an operation that is newer than the operation
|
// for which we are doing the dependency check so it is
|
// not possible to find another operation with some dependency.
|
// break the loop to avoid going through the potentially large
|
// list of pending changes.
|
break;
|
}
|
}
|
return hasDependencies;
|
}
|
|
/**
|
* Check if the given ModifyDNMsg has some dependencies on any
|
* currently running previous operation.
|
* Update the dependency list in the associated PendingChange if
|
* there are some dependencies.
|
*
|
* Modify DN Operation depends on
|
* - AddOperation done on the same DN as the target DN of the MODDN operation
|
* - AddOperation done on the new parent of the MODDN operation
|
* - DeleteOperation done on the new DN of the MODDN operation
|
* - ModifyDNOperation done from the new DN of the MODDN operation
|
*
|
* @param msg The ModifyDNMsg to be checked.
|
*
|
* @return A boolean indicating if this operation has some dependencies.
|
*/
|
private synchronized boolean checkDependencies(ModifyDNMsg msg)
|
{
|
boolean hasDependencies = false;
|
final CSN csn = msg.getCSN();
|
final PendingChange change = pendingChanges.get(csn);
|
if (change == null)
|
{
|
return false;
|
}
|
|
final DN targetDN = change.getLDAPUpdateMsg().getDN();
|
|
for (PendingChange pendingChange : pendingChanges.values())
|
{
|
if (pendingChange.getCSN().isOlderThan(csn))
|
{
|
final LDAPUpdateMsg pendingMsg = pendingChange.getLDAPUpdateMsg();
|
if (pendingMsg != null)
|
{
|
if (pendingMsg instanceof DeleteMsg)
|
{
|
// Check if the target of the Delete is the same
|
// as the new DN of this ModifyDN
|
if (msg.newDNIsEqual(pendingMsg.getDN()))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof AddMsg)
|
{
|
// Check if the Add Operation was done on the new parent of
|
// the MODDN operation
|
if (msg.newParentIsEqual(pendingMsg.getDN()))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
// Check if the AddOperation was done on the same DN as the
|
// target DN of the MODDN operation
|
if (pendingMsg.getDN().equals(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof ModifyDNMsg)
|
{
|
// Check if the ModifyDNOperation was done from the new DN of
|
// the MODDN operation
|
if (msg.newDNIsEqual(pendingMsg.getDN()))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
}
|
}
|
else
|
{
|
// We reached an operation that is newer than the operation
|
// for which we are doing the dependency check so it is
|
// not possible to find another operation with some dependency.
|
// break the loop to avoid going through the potentially large
|
// list of pending changes.
|
break;
|
}
|
}
|
return hasDependencies;
|
}
|
|
/**
|
* Check if the given DeleteOperation has some dependencies on any
|
* currently running previous operation.
|
* Update the dependency list in the associated PendingChange if
|
* there are some dependencies.
|
*
|
* DeleteOperation depends on
|
* - DeleteOperation done on children DN
|
* - ModifyDnOperation with target DN that are children of the DEL DN
|
* - AddOperation done on the same DN
|
*
|
*
|
* @param op The DeleteOperation to be checked.
|
*
|
* @return A boolean indicating if this operation has some dependencies.
|
*/
|
public synchronized boolean checkDependencies(DeleteOperation op)
|
{
|
boolean hasDependencies = false;
|
final DN targetDN = op.getEntryDN();
|
final CSN csn = OperationContext.getCSN(op);
|
final PendingChange change = pendingChanges.get(csn);
|
if (change == null)
|
{
|
return false;
|
}
|
|
for (PendingChange pendingChange : pendingChanges.values())
|
{
|
if (pendingChange.getCSN().isOlderThan(csn))
|
{
|
final LDAPUpdateMsg pendingMsg = pendingChange.getLDAPUpdateMsg();
|
if (pendingMsg != null)
|
{
|
if (pendingMsg instanceof DeleteMsg)
|
{
|
/*
|
* Check if the operation to be run is a deleteOperation on a
|
* children of the current DeleteOperation.
|
*/
|
if (pendingMsg.getDN().isDescendantOf(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof AddMsg)
|
{
|
/*
|
* Check if the operation to be run is an addOperation on a
|
* parent of the current DeleteOperation.
|
*/
|
if (pendingMsg.getDN().equals(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
else if (pendingMsg instanceof ModifyDNMsg)
|
{
|
final ModifyDNMsg pendingModDn = (ModifyDNMsg) pendingMsg;
|
/*
|
* Check if the operation to be run is an ModifyDNOperation
|
* on a children of the current DeleteOperation
|
*/
|
if (pendingMsg.getDN().isDescendantOf(targetDN)
|
|| pendingModDn.newDNIsParent(targetDN))
|
{
|
hasDependencies = true;
|
addDependency(change, pendingChange);
|
}
|
}
|
}
|
}
|
else
|
{
|
// We reached an operation that is newer than the operation
|
// for which we are doing the dependency check so it is
|
// not possible to find another operation with some dependency.
|
// break the loop to avoid going through the potentially large
|
// list of pending changes.
|
break;
|
}
|
}
|
return hasDependencies;
|
}
|
|
/**
|
* Check the dependencies of a given Operation/UpdateMsg.
|
*
|
* @param op The Operation for which dependencies must be checked.
|
* @param msg The LocalizableMessage for which dependencies must be checked.
|
* @return A boolean indicating if an operation cannot be replayed
|
* because of dependencies.
|
*/
|
public boolean checkDependencies(Operation op, LDAPUpdateMsg msg)
|
{
|
if (op instanceof ModifyOperation)
|
{
|
ModifyOperation newOp = (ModifyOperation) op;
|
return checkDependencies(newOp);
|
|
} else if (op instanceof DeleteOperation)
|
{
|
DeleteOperation newOp = (DeleteOperation) op;
|
return checkDependencies(newOp);
|
|
} else if (op instanceof AddOperation)
|
{
|
AddOperation newOp = (AddOperation) op;
|
return checkDependencies(newOp);
|
} else if (op instanceof ModifyDNOperationBasis)
|
{
|
ModifyDNMsg newMsg = (ModifyDNMsg) msg;
|
return checkDependencies(newMsg);
|
} else
|
{
|
return true; // unknown type of operation ?!
|
}
|
}
|
}
|