| | |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | | |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.ModificationType; |
| | | import org.opends.server.replication.common.CSN; |
| | | import org.opends.server.types.*; |
| | | |
| | | /** |
| | | * This class is used to store historical information for multiple valued |
| | |
| | | public class AttrHistoricalMultiple extends AttrHistorical |
| | | { |
| | | /** Last time when the attribute was deleted. */ |
| | | private ChangeNumber deleteTime; |
| | | private CSN deleteTime; |
| | | /** Last time the attribute was modified. */ |
| | | private ChangeNumber lastUpdateTime; |
| | | private CSN lastUpdateTime; |
| | | /** |
| | | * Change history for the values of this attribute. We are using a |
| | | * LinkedHashMap here because we want: |
| | | * <ol> |
| | | * <li>Fast access for removing/adding a AttrValueHistorical keyed by the |
| | | * AttributeValue => Use a Map</li> |
| | | * <li>Ordering changes according to the changeNumber of each changes => Use a |
| | | * <li>Ordering changes according to the CSN of each changes => Use a |
| | | * LinkedHashMap</li> |
| | | * </ol> |
| | | */ |
| | |
| | | * @param updateTime the last time this attribute was updated |
| | | * @param valuesHist the new attribute values when updated. |
| | | */ |
| | | public AttrHistoricalMultiple(ChangeNumber deleteTime, |
| | | ChangeNumber updateTime, |
| | | public AttrHistoricalMultiple(CSN deleteTime, |
| | | CSN updateTime, |
| | | Map<AttrValueHistorical,AttrValueHistorical> valuesHist) |
| | | { |
| | | this.deleteTime = deleteTime; |
| | |
| | | * Returns the last time when the attribute was updated. |
| | | * @return the last time when the attribute was updated |
| | | */ |
| | | private ChangeNumber getLastUpdateTime() |
| | | private CSN getLastUpdateTime() |
| | | { |
| | | return lastUpdateTime; |
| | | } |
| | |
| | | * @return the last time when the attribute was deleted |
| | | */ |
| | | @Override |
| | | public ChangeNumber getDeleteTime() |
| | | public CSN getDeleteTime() |
| | | { |
| | | return deleteTime; |
| | | } |
| | | |
| | | /** |
| | | * Duplicate an object. |
| | | * ChangeNumber are duplicated by references |
| | | * @return the duplicated object. |
| | | * |
| | | * Duplicate an object. CSNs are duplicated by references. |
| | | * <p> |
| | | * Method only called in tests |
| | | * |
| | | * @return the duplicated object. |
| | | */ |
| | | AttrHistoricalMultiple duplicate() |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Delete all historical information that is older than |
| | | * the provided ChangeNumber for this attribute type. |
| | | * Delete all historical information that is older than the provided CSN for |
| | | * this attribute type. |
| | | * Add the delete attribute state information |
| | | * @param CN time when the delete was done |
| | | * @param csn time when the delete was done |
| | | */ |
| | | protected void delete(ChangeNumber CN) |
| | | protected void delete(CSN csn) |
| | | { |
| | | // iterate through the values in the valuesInfo |
| | | // and suppress all the values that have not been added |
| | | // after the date of this delete. |
| | | // iterate through the values in the valuesInfo and suppress all the values |
| | | // that have not been added after the date of this delete. |
| | | Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator(); |
| | | while (it.hasNext()) |
| | | { |
| | | AttrValueHistorical info = it.next(); |
| | | if (CN.newerOrEquals(info.getValueUpdateTime()) && |
| | | CN.newerOrEquals(info.getValueDeleteTime())) |
| | | if (csn.newerOrEquals(info.getValueUpdateTime()) && |
| | | csn.newerOrEquals(info.getValueDeleteTime())) |
| | | it.remove(); |
| | | } |
| | | |
| | | if (CN.newer(deleteTime)) |
| | | if (csn.newer(deleteTime)) |
| | | { |
| | | deleteTime = CN; |
| | | deleteTime = csn; |
| | | } |
| | | |
| | | if (CN.newer(lastUpdateTime)) |
| | | if (csn.newer(lastUpdateTime)) |
| | | { |
| | | lastUpdateTime = CN; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | |
| | |
| | | * Update the historical of this attribute after a delete value. |
| | | * |
| | | * @param val value that was deleted |
| | | * @param CN time when the delete was done |
| | | * @param csn time when the delete was done |
| | | */ |
| | | protected void delete(AttributeValue val, ChangeNumber CN) |
| | | protected void delete(AttributeValue val, CSN csn) |
| | | { |
| | | AttrValueHistorical info = new AttrValueHistorical(val, null, CN); |
| | | AttrValueHistorical info = new AttrValueHistorical(val, null, csn); |
| | | valuesHist.remove(info); |
| | | valuesHist.put(info, info); |
| | | if (CN.newer(lastUpdateTime)) |
| | | if (csn.newer(lastUpdateTime)) |
| | | { |
| | | lastUpdateTime = CN; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | |
| | |
| | | * @param attr |
| | | * the attribute containing the set of values that were |
| | | * deleted |
| | | * @param CN |
| | | * @param csn |
| | | * time when the delete was done |
| | | */ |
| | | protected void delete(Attribute attr, ChangeNumber CN) |
| | | protected void delete(Attribute attr, CSN csn) |
| | | { |
| | | for (AttributeValue val : attr) |
| | | { |
| | | AttrValueHistorical info = new AttrValueHistorical(val, null, CN); |
| | | AttrValueHistorical info = new AttrValueHistorical(val, null, csn); |
| | | valuesHist.remove(info); |
| | | valuesHist.put(info, info); |
| | | if (CN.newer(lastUpdateTime)) |
| | | if (csn.newer(lastUpdateTime)) |
| | | { |
| | | lastUpdateTime = CN; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | } |
| | |
| | | * |
| | | * @param addedValue |
| | | * values that was added |
| | | * @param CN |
| | | * @param csn |
| | | * time when the value was added |
| | | */ |
| | | protected void add(AttributeValue addedValue, ChangeNumber CN) |
| | | protected void add(AttributeValue addedValue, CSN csn) |
| | | { |
| | | AttrValueHistorical info = new AttrValueHistorical(addedValue, CN, null); |
| | | AttrValueHistorical info = new AttrValueHistorical(addedValue, csn, null); |
| | | valuesHist.remove(info); |
| | | valuesHist.put(info, info); |
| | | if (CN.newer(lastUpdateTime)) |
| | | if (csn.newer(lastUpdateTime)) |
| | | { |
| | | lastUpdateTime = CN; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | |
| | |
| | | * |
| | | * @param attr |
| | | * the attribute containing the set of added values |
| | | * @param CN |
| | | * @param csn |
| | | * time when the add is done |
| | | */ |
| | | private void add(Attribute attr, ChangeNumber CN) |
| | | private void add(Attribute attr, CSN csn) |
| | | { |
| | | for (AttributeValue val : attr) |
| | | { |
| | | AttrValueHistorical info = new AttrValueHistorical(val, CN, null); |
| | | AttrValueHistorical info = new AttrValueHistorical(val, csn, null); |
| | | valuesHist.remove(info); |
| | | valuesHist.put(info, info); |
| | | if (CN.newer(lastUpdateTime)) |
| | | if (csn.newer(lastUpdateTime)) |
| | | { |
| | | lastUpdateTime = CN; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | } |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean replayOperation( |
| | | Iterator<Modification> modsIterator, ChangeNumber changeNumber, |
| | | public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn, |
| | | Entry modifiedEntry, Modification m) |
| | | { |
| | | // We are replaying an operation that was already done |
| | | // on another master server and this operation has a potential |
| | | // conflict with some more recent operations on this same entry |
| | | // we need to take the more complex path to solve them |
| | | if ((ChangeNumber.compare(changeNumber, getLastUpdateTime()) < 0) || |
| | | if ((CSN.compare(csn, getLastUpdateTime()) < 0) || |
| | | (m.getModificationType() != ModificationType.REPLACE)) |
| | | { |
| | | // the attribute was modified after this change -> conflict |
| | |
| | | switch (m.getModificationType()) |
| | | { |
| | | case DELETE: |
| | | if (changeNumber.older(getDeleteTime())) |
| | | if (csn.older(getDeleteTime())) |
| | | { |
| | | /* this delete is already obsoleted by a more recent delete |
| | | * skip this mod |
| | |
| | | break; |
| | | } |
| | | |
| | | if (!conflictDelete(changeNumber, m, modifiedEntry)) |
| | | if (!conflictDelete(csn, m, modifiedEntry)) |
| | | { |
| | | modsIterator.remove(); |
| | | } |
| | | break; |
| | | |
| | | case ADD: |
| | | conflictAdd(changeNumber, m, modsIterator); |
| | | conflictAdd(csn, m, modsIterator); |
| | | break; |
| | | |
| | | case REPLACE: |
| | | if (changeNumber.older(getDeleteTime())) |
| | | if (csn.older(getDeleteTime())) |
| | | { |
| | | /* this replace is already obsoleted by a more recent delete |
| | | * skip this mod |
| | |
| | | Attribute addedValues = m.getAttribute(); |
| | | m.setAttribute(new AttributeBuilder(addedValues, true).toAttribute()); |
| | | |
| | | conflictDelete(changeNumber, m, modifiedEntry); |
| | | conflictDelete(csn, m, modifiedEntry); |
| | | Attribute keptValues = m.getAttribute(); |
| | | |
| | | m.setAttribute(addedValues); |
| | | conflictAdd(changeNumber, m, modsIterator); |
| | | conflictAdd(csn, m, modsIterator); |
| | | |
| | | AttributeBuilder builder = new AttributeBuilder(keptValues); |
| | | builder.addAll(m.getAttribute()); |
| | |
| | | } |
| | | else |
| | | { |
| | | processLocalOrNonConflictModification(changeNumber, m); |
| | | processLocalOrNonConflictModification(csn, m); |
| | | return false;// the attribute was not modified more recently |
| | | } |
| | | } |
| | |
| | | * It does not check if the operation to process is conflicting or not with |
| | | * previous operations. The caller is responsible for this. |
| | | * |
| | | * @param changeNumber The changeNumber of the operation to process |
| | | * @param csn The CSN of the operation to process |
| | | * @param mod The modify operation to process. |
| | | */ |
| | | @Override |
| | | public void processLocalOrNonConflictModification(ChangeNumber changeNumber, |
| | | Modification mod) |
| | | public void processLocalOrNonConflictModification(CSN csn, Modification mod) |
| | | { |
| | | /* |
| | | * The operation is either a non-conflicting operation or a local |
| | |
| | | case DELETE: |
| | | if (modAttr.isEmpty()) |
| | | { |
| | | delete(changeNumber); |
| | | delete(csn); |
| | | } |
| | | else |
| | | { |
| | | delete(modAttr, changeNumber); |
| | | delete(modAttr, csn); |
| | | } |
| | | break; |
| | | |
| | | case ADD: |
| | | if (type.isSingleValue()) |
| | | { |
| | | delete(changeNumber); |
| | | delete(csn); |
| | | } |
| | | add(modAttr, changeNumber); |
| | | add(modAttr, csn); |
| | | break; |
| | | |
| | | case REPLACE: |
| | | /* TODO : can we replace specific attribute values ????? */ |
| | | delete(changeNumber); |
| | | add(modAttr, changeNumber); |
| | | delete(csn); |
| | | add(modAttr, csn); |
| | | break; |
| | | |
| | | case INCREMENT: |
| | | /* FIXME : we should update ChangeNumber */ |
| | | /* FIXME : we should update CSN */ |
| | | break; |
| | | } |
| | | } |
| | |
| | | * Process a delete attribute values that is conflicting with a previous |
| | | * modification. |
| | | * |
| | | * @param changeNumber The changeNumber of the currently processed change |
| | | * @param csn The CSN of the currently processed change |
| | | * @param m the modification that is being processed |
| | | * @param modifiedEntry the entry that is modified (before current mod) |
| | | * @return false if there is nothing to do |
| | | */ |
| | | private boolean conflictDelete(ChangeNumber changeNumber, Modification m, |
| | | Entry modifiedEntry) |
| | | private boolean conflictDelete(CSN csn, Modification m, Entry modifiedEntry) |
| | | { |
| | | /* |
| | | * We are processing a conflicting DELETE modification |
| | |
| | | { |
| | | AttrValueHistorical valInfo = it.next(); |
| | | |
| | | if (changeNumber.older(valInfo.getValueUpdateTime())) |
| | | if (csn.older(valInfo.getValueUpdateTime())) |
| | | { |
| | | /* |
| | | * this value has been updated after this delete, therefore |
| | |
| | | * information unless it is a Deleted attribute value that is |
| | | * more recent than this DELETE |
| | | */ |
| | | if (changeNumber.newerOrEquals(valInfo.getValueDeleteTime())) |
| | | if (csn.newerOrEquals(valInfo.getValueDeleteTime())) |
| | | { |
| | | it.remove(); |
| | | } |
| | |
| | | |
| | | m.setAttribute(builder.toAttribute()); |
| | | |
| | | if (changeNumber.newer(getDeleteTime())) |
| | | if (csn.newer(getDeleteTime())) |
| | | { |
| | | deleteTime = changeNumber; |
| | | deleteTime = csn; |
| | | } |
| | | if (changeNumber.newer(getLastUpdateTime())) |
| | | if (csn.newer(getLastUpdateTime())) |
| | | { |
| | | lastUpdateTime = changeNumber; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | else |
| | |
| | | |
| | | /* update historical information */ |
| | | AttrValueHistorical valInfo = |
| | | new AttrValueHistorical(val, null, changeNumber); |
| | | new AttrValueHistorical(val, null, csn); |
| | | AttrValueHistorical oldValInfo = valuesHist.get(valInfo); |
| | | if (oldValInfo != null) |
| | | { |
| | | /* this value already exist in the historical information */ |
| | | if (changeNumber.equals(oldValInfo.getValueUpdateTime())) |
| | | if (csn.equals(oldValInfo.getValueUpdateTime())) |
| | | { |
| | | // This value was added earlier in the same operation |
| | | // we need to keep the delete. |
| | | addedInCurrentOp = true; |
| | | } |
| | | if (changeNumber.newerOrEquals(oldValInfo.getValueDeleteTime()) && |
| | | changeNumber.newerOrEquals(oldValInfo.getValueUpdateTime())) |
| | | if (csn.newerOrEquals(oldValInfo.getValueDeleteTime()) && |
| | | csn.newerOrEquals(oldValInfo.getValueUpdateTime())) |
| | | { |
| | | valuesHist.remove(oldValInfo); |
| | | valuesHist.put(valInfo, valInfo); |
| | |
| | | |
| | | m.setAttribute(builder.toAttribute()); |
| | | |
| | | if (changeNumber.newer(getLastUpdateTime())) |
| | | if (csn.newer(getLastUpdateTime())) |
| | | { |
| | | lastUpdateTime = changeNumber; |
| | | lastUpdateTime = csn; |
| | | } |
| | | } |
| | | |
| | |
| | | * Process a add attribute values that is conflicting with a previous |
| | | * modification. |
| | | * |
| | | * @param changeNumber the historical info associated to the entry |
| | | * @param csn the historical info associated to the entry |
| | | * @param m the modification that is being processed |
| | | * @param modsIterator iterator on the list of modification |
| | | * @return false if operation becomes empty and must not be processed |
| | | */ |
| | | private boolean conflictAdd(ChangeNumber changeNumber, Modification m, |
| | | private boolean conflictAdd(CSN csn, Modification m, |
| | | Iterator<Modification> modsIterator) |
| | | { |
| | | /* |
| | |
| | | * real entry |
| | | */ |
| | | |
| | | if (changeNumber.older(getDeleteTime())) |
| | | if (csn.older(getDeleteTime())) |
| | | { |
| | | /* A delete has been done more recently than this add |
| | | * forget this MOD ADD |
| | |
| | | for (AttributeValue addVal : m.getAttribute()) |
| | | { |
| | | AttrValueHistorical valInfo = |
| | | new AttrValueHistorical(addVal, changeNumber, null); |
| | | new AttrValueHistorical(addVal, csn, null); |
| | | AttrValueHistorical oldValInfo = valuesHist.get(valInfo); |
| | | if (oldValInfo == null) |
| | | { |
| | |
| | | * in all cases suppress this value from the value list |
| | | * as it is already present in the entry |
| | | */ |
| | | if (changeNumber.newer(oldValInfo.getValueUpdateTime())) |
| | | if (csn.newer(oldValInfo.getValueUpdateTime())) |
| | | { |
| | | valuesHist.remove(oldValInfo); |
| | | valuesHist.put(valInfo, valInfo); |
| | |
| | | /* this value is marked as a deleted value |
| | | * check if this mod is more recent the this delete |
| | | */ |
| | | if (changeNumber.newerOrEquals(oldValInfo.getValueDeleteTime())) |
| | | if (csn.newerOrEquals(oldValInfo.getValueDeleteTime())) |
| | | { |
| | | /* this add is more recent, |
| | | * remove the old delete historical information |
| | |
| | | modsIterator.remove(); |
| | | } |
| | | |
| | | if (changeNumber.newer(getLastUpdateTime())) |
| | | if (csn.newer(getLastUpdateTime())) |
| | | { |
| | | lastUpdateTime = changeNumber; |
| | | lastUpdateTime = csn; |
| | | } |
| | | |
| | | return true; |
| | |
| | | */ |
| | | @Override |
| | | public void assign(HistAttrModificationKey histKey, AttributeValue value, |
| | | ChangeNumber cn) |
| | | CSN csn) |
| | | { |
| | | switch (histKey) |
| | | { |
| | | case ADD: |
| | | if (value != null) |
| | | { |
| | | add(value, cn); |
| | | add(value, csn); |
| | | } |
| | | break; |
| | | |
| | | case DEL: |
| | | if (value != null) |
| | | { |
| | | delete(value, cn); |
| | | delete(value, csn); |
| | | } |
| | | break; |
| | | |
| | | case REPL: |
| | | delete(cn); |
| | | delete(csn); |
| | | if (value != null) |
| | | { |
| | | add(value, cn); |
| | | add(value, csn); |
| | | } |
| | | break; |
| | | |
| | | case DELATTR: |
| | | delete(cn); |
| | | delete(csn); |
| | | break; |
| | | } |
| | | } |