AttrHistoricalMultiple.java revision f61444ce38af62d66efd549a90c9a958bde95691
0N/A/*
1585N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License, Version 1.0 only
0N/A * (the "License"). You may not use this file except in compliance
0N/A * with the License.
0N/A *
0N/A * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
0N/A * or http://forgerock.org/license/CDDLv1.0.html.
0N/A * See the License for the specific language governing permissions
0N/A * and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at legal-notices/CDDLv1_0.txt.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information:
1472N/A * Portions Copyright [yyyy] [name of copyright owner]
1472N/A *
1472N/A * CDDL HEADER END
0N/A *
0N/A *
0N/A * Copyright 2006-2010 Sun Microsystems, Inc.
1879N/A * Portions Copyright 2011-2015 ForgeRock AS
1879N/A */
1879N/Apackage org.opends.server.replication.plugin;
1879N/A
1879N/Aimport java.util.Iterator;
1879N/Aimport java.util.LinkedHashMap;
1879N/Aimport java.util.Map;
0N/Aimport java.util.Set;
0N/A
0N/Aimport org.forgerock.opendj.ldap.ByteString;
0N/Aimport org.forgerock.opendj.ldap.ModificationType;
0N/Aimport org.opends.server.replication.common.CSN;
0N/Aimport org.opends.server.types.Attribute;
0N/Aimport org.opends.server.types.AttributeBuilder;
0N/Aimport org.opends.server.types.AttributeType;
0N/Aimport org.opends.server.types.Entry;
0N/Aimport org.opends.server.types.Modification;
0N/A
1585N/A/**
1585N/A * This class is used to store historical information for multiple valued
0N/A * attributes.
0N/A * One object of this type is created for each attribute that was changed in
113N/A * the entry.
0N/A * It allows to record the last time a given value was added, the last
113N/A * time a given value was deleted and the last time the whole attribute was
113N/A * deleted.
0N/A */
0N/Apublic class AttrHistoricalMultiple extends AttrHistorical
113N/A{
113N/A /** Last time when the attribute was deleted. */
0N/A private CSN deleteTime;
0N/A /** Last time the attribute was modified. */
0N/A private CSN lastUpdateTime;
0N/A /**
0N/A * Change history for the values of this attribute. We are using a
113N/A * LinkedHashMap here because we want:
0N/A * <ol>
1756N/A * <li>Fast access for removing/adding a AttrValueHistorical keyed by the
0N/A * attribute value => Use a Map</li>
0N/A * <li>Ordering changes according to the CSN of each changes => Use a
0N/A * LinkedHashMap</li>
0N/A * </ol>
0N/A */
0N/A private final Map<AttrValueHistorical, AttrValueHistorical> valuesHist = new LinkedHashMap<>();
0N/A
0N/A /**
0N/A * Create a new object from the provided information.
0N/A * @param deleteTime the last time this attribute was deleted
0N/A * @param updateTime the last time this attribute was updated
0N/A * @param valuesHist the new attribute values when updated.
0N/A */
0N/A public AttrHistoricalMultiple(CSN deleteTime,
0N/A CSN updateTime,
0N/A Map<AttrValueHistorical,AttrValueHistorical> valuesHist)
0N/A {
0N/A this.deleteTime = deleteTime;
0N/A this.lastUpdateTime = updateTime;
0N/A if (valuesHist != null)
0N/A {
0N/A this.valuesHist.putAll(valuesHist);
0N/A }
0N/A }
679N/A
679N/A /**
679N/A * Create a new object.
0N/A */
0N/A public AttrHistoricalMultiple()
0N/A {
0N/A this.deleteTime = null;
0N/A this.lastUpdateTime = null;
0N/A }
0N/A
0N/A /**
0N/A * Returns the last time when the attribute was updated.
0N/A * @return the last time when the attribute was updated
0N/A */
1630N/A private CSN getLastUpdateTime()
1630N/A {
1630N/A return lastUpdateTime;
1630N/A }
1630N/A
0N/A @Override
1145N/A public CSN getDeleteTime()
1145N/A {
1145N/A return deleteTime;
0N/A }
0N/A
0N/A /**
0N/A * Duplicate an object. CSNs are duplicated by references.
0N/A * <p>
0N/A * Method only called in tests
0N/A *
0N/A * @return the duplicated object.
0N/A */
0N/A AttrHistoricalMultiple duplicate()
0N/A {
0N/A return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime, this.valuesHist);
0N/A }
0N/A
0N/A /**
0N/A * Delete all historical information that is older than the provided CSN for
0N/A * this attribute type.
0N/A * Add the delete attribute state information
0N/A * @param csn time when the delete was done
695N/A */
1756N/A void delete(CSN csn)
695N/A {
0N/A // iterate through the values in the valuesInfo and suppress all the values
0N/A // that have not been added after the date of this delete.
113N/A Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator();
0N/A while (it.hasNext())
0N/A {
0N/A AttrValueHistorical info = it.next();
0N/A if (csn.isNewerThanOrEqualTo(info.getValueUpdateTime()) &&
0N/A csn.isNewerThanOrEqualTo(info.getValueDeleteTime()))
0N/A {
0N/A it.remove();
0N/A }
0N/A }
0N/A
0N/A if (csn.isNewerThan(deleteTime))
0N/A {
0N/A deleteTime = csn;
0N/A }
0N/A
0N/A if (csn.isNewerThan(lastUpdateTime))
0N/A {
0N/A lastUpdateTime = csn;
679N/A }
1756N/A }
679N/A
679N/A /**
679N/A * Update the historical of this attribute after deleting a set of values.
0N/A *
0N/A * @param attr
0N/A * the attribute containing the set of values that were deleted
0N/A * @param csn
0N/A * time when the delete was done
0N/A */
0N/A void delete(Attribute attr, CSN csn)
0N/A {
0N/A for (ByteString val : attr)
0N/A {
0N/A delete(val, csn);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Update the historical of this attribute after a delete value.
0N/A *
0N/A * @param val value that was deleted
0N/A * @param csn time when the delete was done
0N/A */
0N/A void delete(ByteString val, CSN csn)
0N/A {
0N/A update(csn, new AttrValueHistorical(val, null, csn));
0N/A }
1145N/A
1145N/A /**
1145N/A * Update the historical information when values are added.
1145N/A *
1145N/A * @param attr
1145N/A * the attribute containing the set of added values
1145N/A * @param csn
1145N/A * time when the add is done
1145N/A */
1630N/A private void add(Attribute attr, CSN csn)
1630N/A {
1630N/A for (ByteString val : attr)
1630N/A {
1630N/A add(val, csn);
1630N/A }
0N/A }
1630N/A
1630N/A /**
1630N/A * Update the historical information when a value is added.
0N/A *
0N/A * @param addedValue
1630N/A * values that was added
1630N/A * @param csn
1630N/A * time when the value was added
0N/A */
0N/A void add(ByteString addedValue, CSN csn)
0N/A {
0N/A update(csn, new AttrValueHistorical(addedValue, csn, null));
0N/A }
0N/A
1630N/A private void update(CSN csn, AttrValueHistorical info)
0N/A {
1630N/A valuesHist.remove(info);
0N/A valuesHist.put(info, info);
0N/A if (csn.isNewerThan(lastUpdateTime))
0N/A {
0N/A lastUpdateTime = csn;
0N/A }
1630N/A }
0N/A
1630N/A @Override
0N/A public Set<AttrValueHistorical> getValuesHistorical()
0N/A {
0N/A return valuesHist.keySet();
0N/A }
0N/A
113N/A @Override
113N/A public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn,
113N/A Entry modifiedEntry, Modification m)
113N/A {
0N/A // We are replaying an operation that was already done
0N/A // on another master server and this operation has a potential
0N/A // conflict with some more recent operations on this same entry
0N/A // we need to take the more complex path to solve them
0N/A if (CSN.compare(csn, getLastUpdateTime()) < 0 ||
0N/A m.getModificationType() != ModificationType.REPLACE)
0N/A {
0N/A // the attribute was modified after this change -> conflict
0N/A
0N/A switch (m.getModificationType().asEnum())
0N/A {
2941N/A case DELETE:
2941N/A if (csn.isOlderThan(getDeleteTime()))
2941N/A {
2941N/A /* this delete is already obsoleted by a more recent delete
0N/A * skip this mod
0N/A */
0N/A modsIterator.remove();
113N/A break;
113N/A }
0N/A
0N/A if (!conflictDelete(csn, m, modifiedEntry))
113N/A {
113N/A modsIterator.remove();
0N/A }
0N/A break;
0N/A
113N/A case ADD:
113N/A conflictAdd(csn, m, modsIterator);
113N/A break;
113N/A
113N/A case REPLACE:
113N/A if (csn.isOlderThan(getDeleteTime()))
113N/A {
113N/A /* this replace is already obsoleted by a more recent delete
113N/A * skip this mod
113N/A */
0N/A modsIterator.remove();
0N/A break;
0N/A }
0N/A
0N/A /* save the values that are added by the replace operation
113N/A * into addedValues
113N/A * first process the replace as a delete operation -> this generate
113N/A * a list of values that should be kept
0N/A * then process the addedValues as if they were coming from a add
113N/A * -> this generate the list of values that needs to be added
113N/A * concatenate the 2 generated lists into a replace
0N/A */
0N/A Attribute addedValues = m.getAttribute();
0N/A m.setAttribute(new AttributeBuilder(addedValues, true).toAttribute());
0N/A
113N/A conflictDelete(csn, m, modifiedEntry);
113N/A Attribute keptValues = m.getAttribute();
113N/A
113N/A m.setAttribute(addedValues);
0N/A conflictAdd(csn, m, modsIterator);
0N/A
0N/A AttributeBuilder builder = new AttributeBuilder(keptValues);
0N/A builder.addAll(m.getAttribute());
0N/A m.setAttribute(builder.toAttribute());
0N/A break;
0N/A
0N/A case INCREMENT:
0N/A // TODO : FILL ME
0N/A break;
0N/A }
0N/A return true;
0N/A }
0N/A else
0N/A {
0N/A processLocalOrNonConflictModification(csn, m);
0N/A return false;// the attribute was not modified more recently
0N/A }
0N/A }
0N/A
534N/A @Override
0N/A public void processLocalOrNonConflictModification(CSN csn, Modification mod)
113N/A {
695N/A /*
0N/A * The operation is either a non-conflicting operation or a local operation
0N/A * so there is no need to check the historical information for conflicts.
695N/A * If this is a local operation, then this code is run after
1756N/A * the pre-operation phase.
695N/A * If this is a non-conflicting replicated operation, this code is run
0N/A * during the handleConflictResolution().
0N/A */
0N/A
0N/A Attribute modAttr = mod.getAttribute();
0N/A AttributeType type = modAttr.getAttributeType();
0N/A
0N/A switch (mod.getModificationType().asEnum())
534N/A {
0N/A case DELETE:
0N/A if (modAttr.isEmpty())
0N/A {
0N/A delete(csn);
0N/A }
0N/A else
0N/A {
0N/A delete(modAttr, csn);
0N/A }
0N/A break;
0N/A
0N/A case ADD:
0N/A if (type.isSingleValue())
0N/A {
0N/A delete(csn);
0N/A }
0N/A add(modAttr, csn);
0N/A break;
0N/A
0N/A case REPLACE:
0N/A /* TODO : can we replace specific attribute values ????? */
0N/A delete(csn);
0N/A add(modAttr, csn);
0N/A break;
0N/A
0N/A case INCREMENT:
0N/A /* FIXME : we should update CSN */
0N/A break;
0N/A }
113N/A }
0N/A
0N/A /**
0N/A * Process a delete attribute values that is conflicting with a previous
0N/A * modification.
0N/A *
0N/A * @param csn The CSN of the currently processed change
0N/A * @param m the modification that is being processed
0N/A * @param modifiedEntry the entry that is modified (before current mod)
0N/A * @return false if there is nothing to do
1753N/A */
1753N/A private boolean conflictDelete(CSN csn, Modification m, Entry modifiedEntry)
0N/A {
0N/A /*
0N/A * We are processing a conflicting DELETE modification
0N/A *
0N/A * This code is written on the assumption that conflict are
0N/A * rare. We therefore don't care much about the performance
0N/A * However since it is rarely executed this code needs to be
0N/A * as simple as possible to make sure that all paths are tested.
0N/A * In this case the most simple seem to change the DELETE
0N/A * in a REPLACE modification that keeps all values
0N/A * more recent that the DELETE.
0N/A * we are therefore going to change m into a REPLACE that will keep
0N/A * all the values that have been updated after the DELETE time
0N/A * If a value is present in the entry without any state information
0N/A * it must be removed so we simply ignore them
0N/A */
0N/A
0N/A Attribute modAttr = m.getAttribute();
0N/A if (modAttr.isEmpty())
0N/A {
0N/A /*
0N/A * We are processing a DELETE attribute modification
0N/A */
0N/A m.setModificationType(ModificationType.REPLACE);
0N/A AttributeBuilder builder = new AttributeBuilder(modAttr, true);
0N/A
0N/A Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator();
0N/A while (it.hasNext())
0N/A {
0N/A AttrValueHistorical valInfo = it.next();
0N/A
0N/A if (csn.isOlderThan(valInfo.getValueUpdateTime()))
0N/A {
0N/A /*
0N/A * this value has been updated after this delete, therefore
0N/A * this value must be kept
0N/A */
534N/A builder.add(valInfo.getAttributeValue());
534N/A }
534N/A else
534N/A {
679N/A /*
679N/A * this value is going to be deleted, remove it from historical
679N/A * information unless it is a Deleted attribute value that is
0N/A * more recent than this DELETE
534N/A */
0N/A if (csn.isNewerThanOrEqualTo(valInfo.getValueDeleteTime()))
0N/A {
679N/A it.remove();
679N/A }
0N/A }
679N/A }
0N/A
0N/A m.setAttribute(builder.toAttribute());
0N/A
0N/A if (csn.isNewerThan(getDeleteTime()))
0N/A {
0N/A deleteTime = csn;
0N/A }
0N/A if (csn.isNewerThan(getLastUpdateTime()))
0N/A {
0N/A lastUpdateTime = csn;
0N/A }
0N/A }
0N/A else
0N/A {
0N/A // we are processing DELETE of some attribute values
0N/A AttributeBuilder builder = new AttributeBuilder(modAttr);
0N/A
0N/A for (ByteString val : modAttr)
1879N/A {
1879N/A boolean deleteIt = true; // true if the delete must be done
boolean addedInCurrentOp = false;
/* update historical information */
AttrValueHistorical valInfo = new AttrValueHistorical(val, null, csn);
AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
if (oldValInfo != null)
{
/* this value already exist in the historical information */
if (csn.equals(oldValInfo.getValueUpdateTime()))
{
// This value was added earlier in the same operation
// we need to keep the delete.
addedInCurrentOp = true;
}
if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()) &&
csn.isNewerThanOrEqualTo(oldValInfo.getValueUpdateTime()))
{
valuesHist.remove(oldValInfo);
valuesHist.put(valInfo, valInfo);
}
else if (oldValInfo.isUpdate())
{
deleteIt = false;
}
}
else
{
valuesHist.remove(oldValInfo);
valuesHist.put(valInfo, valInfo);
}
/* if the attribute value is not to be deleted
* or if attribute value is not present suppress it from the
* MOD to make sure the delete is going to succeed
*/
if (!deleteIt
|| (!modifiedEntry.hasValue(modAttr.getAttributeType(), modAttr
.getOptions(), val) && ! addedInCurrentOp))
{
// this value was already deleted before and therefore
// this should not be replayed.
builder.remove(val);
if (builder.isEmpty())
{
// This was the last values in the set of values to be deleted.
// this MOD must therefore be skipped.
return false;
}
}
}
m.setAttribute(builder.toAttribute());
if (csn.isNewerThan(getLastUpdateTime()))
{
lastUpdateTime = csn;
}
}
return true;
}
/**
* Process a add attribute values that is conflicting with a previous
* modification.
*
* @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
*/
private void conflictAdd(CSN csn, Modification m, Iterator<Modification> modsIterator)
{
/*
* if historicalattributedelete is newer forget this mod else find
* attr value if does not exist add historicalvalueadded timestamp
* add real value in entry else if timestamp older and already was
* historicalvalueadded update historicalvalueadded else if
* timestamp older and was historicalvaluedeleted change
* historicalvaluedeleted into historicalvalueadded add value in
* real entry
*/
if (csn.isOlderThan(getDeleteTime()))
{
/* A delete has been done more recently than this add
* forget this MOD ADD
*/
modsIterator.remove();
return;
}
AttributeBuilder builder = new AttributeBuilder(m.getAttribute());
for (ByteString addVal : m.getAttribute())
{
AttrValueHistorical valInfo =
new AttrValueHistorical(addVal, csn, null);
AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
if (oldValInfo == null)
{
/* this value does not exist yet
* add it in the historical information
* let the operation process normally
*/
valuesHist.put(valInfo, valInfo);
}
else
{
if (oldValInfo.isUpdate())
{
/* if the value is already present
* check if the updateTime must be updated
* in all cases suppress this value from the value list
* as it is already present in the entry
*/
if (csn.isNewerThan(oldValInfo.getValueUpdateTime()))
{
valuesHist.remove(oldValInfo);
valuesHist.put(valInfo, valInfo);
}
builder.remove(addVal);
}
else
{ // it is a delete
/* this value is marked as a deleted value
* check if this mod is more recent the this delete
*/
if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()))
{
/* this add is more recent,
* remove the old delete historical information
* and add our more recent one
* let the operation process
*/
valuesHist.remove(oldValInfo);
valuesHist.put(valInfo, valInfo);
}
else
{
/* the delete that is present in the historical information
* is more recent so it must win,
* remove this value from the list of values to add
* don't update the historical information
*/
builder.remove(addVal);
}
}
}
}
Attribute attr = builder.toAttribute();
m.setAttribute(attr);
if (attr.isEmpty())
{
modsIterator.remove();
}
if (csn.isNewerThan(getLastUpdateTime()))
{
lastUpdateTime = csn;
}
}
@Override
public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn)
{
switch (histKey)
{
case ADD:
if (value != null)
{
add(value, csn);
}
break;
case DEL:
if (value != null)
{
delete(value, csn);
}
break;
case REPL:
delete(csn);
if (value != null)
{
add(value, csn);
}
break;
case DELATTR:
delete(csn);
break;
}
}
}