| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import java.util.ArrayList; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.messages.MessageHandler.getMessage; |
| | | import static org.opends.server.messages.ReplicationMessages.*; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashSet; |
| | |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ErrorLogCategory; |
| | | import org.opends.server.types.ErrorLogSeverity; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.ModificationType; |
| | | |
| | |
| | | static final AttributeType entryuuidAttrType = |
| | | DirectoryServer.getSchema().getAttributeType(ENTRYUIDNAME); |
| | | |
| | | /* |
| | | * The last update seen on this entry, allows fast conflict detection. |
| | | */ |
| | | private ChangeNumber moreRecentChangenumber = |
| | | new ChangeNumber(0,0,(short)0); |
| | | |
| | | /* |
| | | * contains Historical information for each attribute sorted by attribute type |
| | |
| | | } |
| | | |
| | | /** |
| | | * Duplicates an Historical Object. |
| | | * attributesInfo are nor duplicated but used as references. |
| | | * @return The duplicate of the Historical Object |
| | | */ |
| | | public Historical duplicate() |
| | | { |
| | | Historical dup = new Historical(); |
| | | |
| | | dup.attributesInfo = |
| | | new HashMap<AttributeType,AttrInfoWithOptions>(attributesInfo); |
| | | dup.moreRecentChangenumber = this.moreRecentChangenumber; |
| | | return dup; |
| | | } |
| | | |
| | | /** |
| | | * Process an operation. |
| | | * This method is responsible for detecting and resolving conflict for |
| | | * modifyOperation. This is done by using the historical information. |
| | |
| | | * @return true if the replayed operation was in conflict |
| | | */ |
| | | public boolean replayOperation(ModifyOperation modifyOperation, |
| | | Entry modifiedEntry) |
| | | Entry modifiedEntry) |
| | | { |
| | | boolean bConflict = false; |
| | | List<Modification> mods = modifyOperation.getModifications(); |
| | | ChangeNumber changeNumber = |
| | | OperationContext.getChangeNumber(modifyOperation); |
| | | |
| | | for (Iterator modsIterator = mods.iterator(); modsIterator.hasNext();) |
| | | for (Iterator<Modification> modsIterator = mods.iterator(); |
| | | modsIterator.hasNext(); ) |
| | | { |
| | | Modification m = (Modification) modsIterator.next(); |
| | | Attribute modAttr = m.getAttribute(); |
| | | Set<String> options = modAttr.getOptions(); |
| | | AttributeType type = modAttr.getAttributeType(); |
| | | AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type); |
| | | AttrInfo attrInfo = null; |
| | | if (attrInfoWithOptions != null) |
| | | Modification m = modsIterator.next(); |
| | | |
| | | AttributeInfo attrInfo = getAttrInfo(m); |
| | | |
| | | if (attrInfo.replayOperation(modsIterator, changeNumber, |
| | | modifiedEntry, m)) |
| | | { |
| | | attrInfo = attrInfoWithOptions.get(options); |
| | | } |
| | | |
| | | if (this.hasConflict(attrInfo, changeNumber)) |
| | | { |
| | | // 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 |
| | | |
| | | switch (m.getModificationType()) |
| | | { |
| | | case DELETE: |
| | | if (changeNumber.older(attrInfo.getDeleteTime())) |
| | | { |
| | | /* this delete is already obsoleted by a more recent delete |
| | | * skip this mod |
| | | */ |
| | | modsIterator.remove(); |
| | | break; |
| | | } |
| | | |
| | | this.conflictDelete(changeNumber, |
| | | type, m, modifiedEntry, attrInfo, modAttr); |
| | | break; |
| | | |
| | | case ADD: |
| | | this.conflictAdd(modsIterator, changeNumber, attrInfo, |
| | | modAttr.getValues(), modAttr.getOptions()); |
| | | break; |
| | | |
| | | case REPLACE: |
| | | if (changeNumber.older(attrInfo.getDeleteTime())) |
| | | { |
| | | /* this replace is already obsoleted by a more recent delete |
| | | * skip this mod |
| | | */ |
| | | modsIterator.remove(); |
| | | break; |
| | | } |
| | | /* save the values that are added by the replace operation |
| | | * into addedValues |
| | | * first process the replace as a delete operation -> this generate |
| | | * a list of values that should be kept |
| | | * then process the addedValues as if they were coming from a add |
| | | * -> this generate the list of values that needs to be added |
| | | * concatenate the 2 generated lists into a replace |
| | | */ |
| | | LinkedHashSet<AttributeValue> addedValues = modAttr.getValues(); |
| | | modAttr.setValues(new LinkedHashSet<AttributeValue>()); |
| | | |
| | | this.conflictDelete(changeNumber, type, m, modifiedEntry, |
| | | attrInfo, modAttr); |
| | | |
| | | LinkedHashSet<AttributeValue> keptValues = modAttr.getValues(); |
| | | this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues, |
| | | modAttr.getOptions()); |
| | | keptValues.addAll(addedValues); |
| | | break; |
| | | |
| | | case INCREMENT: |
| | | // TODO : FILL ME |
| | | break; |
| | | } |
| | | bConflict = true; |
| | | } |
| | | else |
| | | { |
| | | processLocalOrNonConflictModification(changeNumber, m); |
| | | } |
| | | } |
| | | |
| | | // TODO : now purge old historical information |
| | | |
| | | if (moreRecentChangenumber == null || |
| | | moreRecentChangenumber.older(changeNumber)) |
| | | { |
| | | moreRecentChangenumber = changeNumber; |
| | | } |
| | | return bConflict; |
| | | } |
| | | |
| | | /** |
| | | * This method calculate the historical information and update the hist |
| | | * attribute to store the historical information for modify operation that |
| | | * does not conflict with previous operation. |
| | | * This is the usual path and should therefore be optimized. |
| | | * |
| | | * 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 mod The modify operation to process. |
| | | */ |
| | | private void processLocalOrNonConflictModification(ChangeNumber changeNumber, |
| | | Modification mod) |
| | | { |
| | | /* |
| | | * The operation is either a non-conflicting operation or a local |
| | | * operation so there is no need to check the historical information |
| | | * for conflicts. |
| | | * If this is a local operation, the this code is run during |
| | | * the pre-operation phase (TODO : should make sure that replication |
| | | * is always run after all other plugins) |
| | | * If this is a non-conflicting replicated operation, this code is run |
| | | * during the handleConflictResolution(). |
| | | */ |
| | | |
| | | Attribute modAttr = mod.getAttribute(); |
| | | if (modAttr.getAttributeType().equals(historicalAttrType)) |
| | | { |
| | | return; |
| | | } |
| | | Set<String> options = modAttr.getOptions(); |
| | | AttributeType type = modAttr.getAttributeType(); |
| | | AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type); |
| | | AttrInfo attrInfo; |
| | | if (attrInfoWithOptions != null) |
| | | { |
| | | attrInfo = attrInfoWithOptions.get(options); |
| | | } |
| | | else |
| | | { |
| | | attrInfo = null; |
| | | } |
| | | |
| | | /* |
| | | * The following code only works for multi-valued attributes. |
| | | * TODO : See impact of single valued attributes |
| | | */ |
| | | if (attrInfo == null) |
| | | { |
| | | attrInfo = new AttrInfo(); |
| | | if (attrInfoWithOptions == null) |
| | | { |
| | | attrInfoWithOptions = new AttrInfoWithOptions(); |
| | | } |
| | | attrInfoWithOptions.put(options, attrInfo); |
| | | attributesInfo.put(type, attrInfoWithOptions); |
| | | } |
| | | switch (mod.getModificationType()) |
| | | { |
| | | case DELETE: |
| | | if (modAttr.getValues().isEmpty()) |
| | | { |
| | | attrInfo.delete(changeNumber); |
| | | } |
| | | else |
| | | { |
| | | attrInfo.delete(modAttr.getValues(), changeNumber); |
| | | } |
| | | break; |
| | | |
| | | case ADD: |
| | | if (type.isSingleValue()) |
| | | { |
| | | attrInfo.delete(changeNumber); |
| | | } |
| | | attrInfo.add(modAttr.getValues(), changeNumber); |
| | | break; |
| | | |
| | | case REPLACE: |
| | | /* TODO : can we replace specific attribute values ????? */ |
| | | attrInfo.delete(changeNumber); |
| | | attrInfo.add(modAttr.getValues(), changeNumber); |
| | | break; |
| | | |
| | | case INCREMENT: |
| | | /* FIXME : we should update ChangeNumber */ |
| | | break; |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Append replacement of state information to a given modification. |
| | | * |
| | | * @param modifyOperation the modification. |
| | | */ |
| | | public void generateState(ModifyOperation modifyOperation) |
| | |
| | | { |
| | | for (Modification mod : mods) |
| | | { |
| | | processLocalOrNonConflictModification(changeNumber, mod); |
| | | } |
| | | if (moreRecentChangenumber == null || |
| | | moreRecentChangenumber.older(changeNumber)) |
| | | { |
| | | moreRecentChangenumber = changeNumber; |
| | | AttributeInfo attrInfo = getAttrInfo(mod); |
| | | if (attrInfo != null) |
| | | attrInfo.processLocalOrNonConflictModification(changeNumber, mod); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the AttrInfo for a given Modification. |
| | | * The AttrInfo is the object that is used to store the historical |
| | | * information of a given attribute type. |
| | | * If there is no historical information for this attribute yet, a new |
| | | * empty AttrInfo is created and returned. |
| | | * |
| | | * @param mod The Modification that must be used. |
| | | * @return The AttrInfo corresponding to the given Modification. |
| | | */ |
| | | private AttributeInfo getAttrInfo(Modification mod) |
| | | { |
| | | Attribute modAttr = mod.getAttribute(); |
| | | if (modAttr.getAttributeType().equals(Historical.historicalAttrType)) |
| | | { |
| | | // Don´t keep historical information for the attribute that is |
| | | // used to store the historical information. |
| | | return null; |
| | | } |
| | | Set<String> options = modAttr.getOptions(); |
| | | AttributeType type = modAttr.getAttributeType(); |
| | | AttrInfoWithOptions attrInfoWithOptions = attributesInfo.get(type); |
| | | AttributeInfo attrInfo; |
| | | if (attrInfoWithOptions != null) |
| | | { |
| | | attrInfo = attrInfoWithOptions.get(options); |
| | | } |
| | | else |
| | | { |
| | | attrInfoWithOptions = new AttrInfoWithOptions(); |
| | | attributesInfo.put(type, attrInfoWithOptions); |
| | | attrInfo = null; |
| | | } |
| | | |
| | | if (attrInfo == null) |
| | | { |
| | | attrInfo = AttributeInfo.createAttributeInfo(type); |
| | | attrInfoWithOptions.put(options, attrInfo); |
| | | } |
| | | return attrInfo; |
| | | } |
| | | |
| | | /** |
| | | * Encode the historical information in an operational attribute. |
| | | * @return The historical information encoded in an operational attribute. |
| | | */ |
| | |
| | | |
| | | { |
| | | AttributeType type = entryWithOptions.getKey(); |
| | | HashMap<Set<String> , AttrInfo> attrwithoptions = |
| | | HashMap<Set<String> , AttributeInfo> attrwithoptions = |
| | | entryWithOptions.getValue().getAttributesInfo(); |
| | | |
| | | for (Map.Entry<Set<String>, AttrInfo> entry : attrwithoptions.entrySet()) |
| | | for (Map.Entry<Set<String>, AttributeInfo> entry : |
| | | attrwithoptions.entrySet()) |
| | | { |
| | | boolean delAttr = false; |
| | | Set<String> options = entry.getKey(); |
| | | String optionsString = ""; |
| | | AttrInfo info = entry.getValue(); |
| | | ChangeNumber deleteTime = info.getDeleteTime(); |
| | | AttributeInfo info = entry.getValue(); |
| | | |
| | | |
| | | if (options != null) |
| | | { |
| | |
| | | optionsString = optionsBuilder.toString(); |
| | | } |
| | | |
| | | ChangeNumber deleteTime = info.getDeleteTime(); |
| | | /* generate the historical information for deleted attributes */ |
| | | if (deleteTime != null) |
| | | { |
| | |
| | | } |
| | | else |
| | | { |
| | | strValue = type.getNormalizedPrimaryName() + optionsString + ":" + |
| | | valInfo.getValueUpdateTime().toString() + |
| | | ":add:" + valInfo.getValue().toString(); |
| | | if (valInfo.getValue() == null) |
| | | { |
| | | strValue = type.getNormalizedPrimaryName() + optionsString |
| | | + ":" + valInfo.getValueUpdateTime().toString() + |
| | | ":add"; |
| | | } |
| | | else |
| | | { |
| | | strValue = type.getNormalizedPrimaryName() + optionsString |
| | | + ":" + valInfo.getValueUpdateTime().toString() + |
| | | ":add:" + valInfo.getValue().toString(); |
| | | } |
| | | } |
| | | |
| | | AttributeValue val = new AttributeValue(historicalAttrType, |
| | |
| | | return attr; |
| | | } |
| | | |
| | | /** |
| | | * Detect if a new change has a potential conflict with previous changes. |
| | | * Must be blinding fast |
| | | * @param info the historical information for the attribute that |
| | | * was modified |
| | | * @param newChange the ChangeNumber of the change for which we need to check |
| | | * for potential conflict |
| | | * @return true if there is a potential conflict, false otherwise |
| | | */ |
| | | private boolean hasConflict(AttrInfo info, ChangeNumber newChange) |
| | | { |
| | | // if I've already seen a change that is more recent than the one |
| | | // that is currently being processed, then there is |
| | | // a potential conflict |
| | | if (ChangeNumber.compare(newChange, moreRecentChangenumber) <= 0) |
| | | { |
| | | if (info == null) |
| | | { |
| | | return false; // the attribute was never modified -> no conflict |
| | | } |
| | | else if (ChangeNumber.compare(newChange, info.getLastUpdateTime()) <= 0) |
| | | { |
| | | return true; // the attribute was modified after this change -> conflict |
| | | } |
| | | else |
| | | { |
| | | return false;// the attribute was not modified more recently |
| | | } |
| | | } |
| | | else |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Process a delete attribute values that is conflicting with a previous |
| | | * modification. |
| | | * |
| | | * @param changeNumber The changeNumber of the currently processed change |
| | | * @param type attribute type |
| | | * @param m the modification that is being processed |
| | | * @param modifiedEntry the entry that is modified (before current mod) |
| | | * @param attrInfo the historical info associated to the entry |
| | | * @param modAttr the attribute modification |
| | | * @return false if there is nothing to do |
| | | */ |
| | | private boolean conflictDelete(ChangeNumber changeNumber, |
| | | AttributeType type, Modification m, |
| | | Entry modifiedEntry, |
| | | AttrInfo attrInfo, Attribute modAttr ) |
| | | { |
| | | LinkedHashSet<AttributeValue> delValues; |
| | | LinkedHashSet<AttributeValue> replValues; |
| | | |
| | | /* |
| | | * We are processing a conflicting DELETE modification |
| | | * |
| | | * This code is written on the assumption that conflict are |
| | | * rare. We therefore don't care much about the performance |
| | | * However since it is rarely executed this code needs to be |
| | | * as simple as possible to make sure that all paths are tested. |
| | | * In this case the most simple seem to change the DELETE |
| | | * in a REPLACE modification that keeps all values |
| | | * more recent that the DELETE. |
| | | * we are therefore going to change m into a REPLACE that will keep |
| | | * all the values that have been updated after the DELETE time |
| | | * If a value is present in the entry without any state information |
| | | * it must be removed so we simply ignore them |
| | | */ |
| | | |
| | | |
| | | |
| | | delValues = modAttr.getValues(); |
| | | if ((delValues == null) || (delValues.isEmpty())) |
| | | { |
| | | /* |
| | | * We are processing a DELETE attribute modification |
| | | */ |
| | | m.setModificationType(ModificationType.REPLACE); |
| | | replValues = new LinkedHashSet<AttributeValue>(); |
| | | |
| | | for (Iterator it = attrInfo.getValuesInfo().iterator(); |
| | | it.hasNext();) |
| | | { |
| | | ValueInfo valInfo; valInfo = (ValueInfo) it.next(); |
| | | |
| | | if (changeNumber.older(valInfo.getValueUpdateTime())) |
| | | { |
| | | /* |
| | | * this value has been updated after this delete, therefore |
| | | * this value must be kept |
| | | */ |
| | | replValues.add(valInfo.getValue()); |
| | | } |
| | | else |
| | | { |
| | | /* |
| | | * this value is going to be deleted, remove it from historical |
| | | * information unless it is a Deleted attribute value that is |
| | | * more recent than this DELETE |
| | | */ |
| | | if (changeNumber.newerOrEquals(valInfo.getValueDeleteTime())) |
| | | { |
| | | it.remove(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | modAttr.setValues(replValues); |
| | | if (changeNumber.newer(attrInfo.getDeleteTime())) |
| | | { |
| | | attrInfo.setDeleteTime(changeNumber); |
| | | } |
| | | if (changeNumber.newer(attrInfo.getLastUpdateTime())) |
| | | { |
| | | attrInfo.setLastUpdateTime(changeNumber); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | /* |
| | | * we are processing DELETE of some attribute values |
| | | */ |
| | | ArrayList<ValueInfo> valuesInfo = attrInfo.getValuesInfo(); |
| | | |
| | | for (Iterator<AttributeValue> delValIterator = delValues.iterator(); |
| | | delValIterator.hasNext();) |
| | | { |
| | | AttributeValue val = delValIterator.next(); |
| | | Boolean deleteIt = true; // true if the delete must be done |
| | | |
| | | /* update historical information */ |
| | | ValueInfo valInfo = new ValueInfo(val, null, changeNumber); |
| | | int index = valuesInfo.indexOf(valInfo); |
| | | if (index != -1) |
| | | { |
| | | /* this value already exist in the historical information */ |
| | | ValueInfo oldValInfo = valuesInfo.get(index); |
| | | if (changeNumber.newer(oldValInfo.getValueDeleteTime()) && |
| | | changeNumber.newer(oldValInfo.getValueUpdateTime())) |
| | | { |
| | | valuesInfo.remove(index); |
| | | valuesInfo.add(valInfo); |
| | | } |
| | | else if (oldValInfo.isUpdate()) |
| | | { |
| | | deleteIt = false; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | valuesInfo.add(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 process again |
| | | */ |
| | | modifiedEntry.getAttribute(type); |
| | | if (!deleteIt |
| | | || !modifiedEntry.hasValue(type, modAttr.getOptions(), val)) |
| | | { |
| | | delValIterator.remove(); |
| | | } |
| | | } |
| | | if (changeNumber.newer(attrInfo.getLastUpdateTime())) |
| | | { |
| | | attrInfo.setLastUpdateTime(changeNumber); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Process a add attribute values that is conflicting with a previous |
| | | * modification. |
| | | * |
| | | * @param modsIterator iterator on the list of modification |
| | | * @param changeNumber the historical info associated to the entry |
| | | * @param attrInfo the historical info associated to the entry |
| | | * @param addValues the values that are added |
| | | * @param options the options that are added |
| | | * @return false if operation becomes empty and must not be processed |
| | | */ |
| | | private boolean conflictAdd(Iterator modsIterator, ChangeNumber changeNumber, |
| | | AttrInfo attrInfo, |
| | | LinkedHashSet<AttributeValue> addValues, |
| | | Set<String> options) |
| | | { |
| | | /* 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 (changeNumber.older(attrInfo.getDeleteTime())) |
| | | { |
| | | /* A delete has been done more recently than this add |
| | | * forget this MOD ADD |
| | | */ |
| | | modsIterator.remove(); |
| | | return false; |
| | | } |
| | | |
| | | for (Iterator<AttributeValue> valIterator = addValues.iterator(); |
| | | valIterator.hasNext();) |
| | | { |
| | | AttributeValue addVal= valIterator.next(); |
| | | ArrayList<ValueInfo> valuesInfo = attrInfo.getValuesInfo(); |
| | | ValueInfo valInfo = new ValueInfo(addVal, changeNumber, null); |
| | | int index = valuesInfo.indexOf(valInfo); |
| | | if (index == -1) |
| | | { |
| | | /* this values does not exist yet |
| | | * add it in the historical information |
| | | * let the operation process normally |
| | | */ |
| | | valuesInfo.add(valInfo); |
| | | } |
| | | else |
| | | { |
| | | ValueInfo oldValueInfo = valuesInfo.get(index); |
| | | if (oldValueInfo.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 (changeNumber.newer(oldValueInfo.getValueUpdateTime())) |
| | | { |
| | | valuesInfo.remove(index); |
| | | valuesInfo.add(valInfo); |
| | | } |
| | | valIterator.remove(); |
| | | } |
| | | else |
| | | { |
| | | /* this value is marked as a deleted value |
| | | * check if this mod is more recent the this delete |
| | | */ |
| | | if (changeNumber.newer(oldValueInfo.getValueDeleteTime())) |
| | | { |
| | | /* this add is more recent, |
| | | * remove the old delete historical information |
| | | * and add our more recent one |
| | | * let the operation process |
| | | */ |
| | | valuesInfo.remove(index); |
| | | valuesInfo.add(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 |
| | | */ |
| | | valIterator.remove(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (addValues.isEmpty()) |
| | | { |
| | | modsIterator.remove(); |
| | | } |
| | | |
| | | if (changeNumber.newer(attrInfo.getLastUpdateTime())) |
| | | { |
| | | attrInfo.setLastUpdateTime(changeNumber); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * read the historical information from the entry attribute and |
| | |
| | | Historical histObj = new Historical(); |
| | | AttributeType lastAttrType = null; |
| | | Set<String> lastOptions = new HashSet<String>(); |
| | | AttrInfo attrInfo = null; |
| | | AttributeInfo attrInfo = null; |
| | | AttrInfoWithOptions attrInfoWithOptions = null; |
| | | |
| | | if (hist == null) |
| | |
| | | return histObj; |
| | | } |
| | | |
| | | for (Attribute attr : hist) |
| | | try |
| | | { |
| | | for (AttributeValue val : attr.getValues()) |
| | | for (Attribute attr : hist) |
| | | { |
| | | HistVal histVal = new HistVal(val.getStringValue()); |
| | | AttributeType attrType = histVal.getAttrType(); |
| | | Set<String> options = histVal.getOptions(); |
| | | ChangeNumber cn = histVal.getCn(); |
| | | AttributeValue value = histVal.getAttributeValue(); |
| | | HistKey histKey = histVal.getHistKey(); |
| | | |
| | | if (attrType == null) |
| | | for (AttributeValue val : attr.getValues()) |
| | | { |
| | | /* |
| | | * This attribute is unknown from the schema |
| | | * Just skip it, the modification will be processed but no |
| | | * historical information is going to be kept. |
| | | * TODO : recovery tool should deal with this, add some logging. |
| | | */ |
| | | continue; |
| | | } |
| | | HistVal histVal = new HistVal(val.getStringValue()); |
| | | AttributeType attrType = histVal.getAttrType(); |
| | | Set<String> options = histVal.getOptions(); |
| | | ChangeNumber cn = histVal.getCn(); |
| | | AttributeValue value = histVal.getAttributeValue(); |
| | | HistKey histKey = histVal.getHistKey(); |
| | | |
| | | /* if attribute type does not match we create new |
| | | * AttrInfoWithOptions and AttrInfo |
| | | * we also add old AttrInfoWithOptions into histObj.attributesInfo |
| | | * if attribute type match but options does not match we create new |
| | | * AttrInfo that we add to AttrInfoWithOptions |
| | | * if both match we keep everything |
| | | */ |
| | | if (attrType != lastAttrType) |
| | | { |
| | | attrInfo = new AttrInfo(); |
| | | attrInfoWithOptions = new AttrInfoWithOptions(); |
| | | attrInfoWithOptions.put(options, attrInfo); |
| | | histObj.attributesInfo.put(attrType, attrInfoWithOptions); |
| | | |
| | | lastAttrType = attrType; |
| | | lastOptions = options; |
| | | } |
| | | else |
| | | { |
| | | if (!options.equals(lastOptions)) |
| | | if (attrType == null) |
| | | { |
| | | attrInfo = new AttrInfo(); |
| | | /* |
| | | * This attribute is unknown from the schema |
| | | * Just skip it, the modification will be processed but no |
| | | * historical information is going to be kept. |
| | | * TODO : REPAIR tool should deal with this, add some logging. |
| | | */ |
| | | continue; |
| | | } |
| | | |
| | | /* if attribute type does not match we create new |
| | | * AttrInfoWithOptions and AttrInfo |
| | | * we also add old AttrInfoWithOptions into histObj.attributesInfo |
| | | * if attribute type match but options does not match we create new |
| | | * AttrInfo that we add to AttrInfoWithOptions |
| | | * if both match we keep everything |
| | | */ |
| | | if (attrType != lastAttrType) |
| | | { |
| | | attrInfo = AttributeInfo.createAttributeInfo(attrType); |
| | | attrInfoWithOptions = new AttrInfoWithOptions(); |
| | | attrInfoWithOptions.put(options, attrInfo); |
| | | histObj.attributesInfo.put(attrType, attrInfoWithOptions); |
| | | |
| | | lastAttrType = attrType; |
| | | lastOptions = options; |
| | | } |
| | | } |
| | | |
| | | if (histObj.moreRecentChangenumber.older(cn)) |
| | | { |
| | | histObj.moreRecentChangenumber = cn; |
| | | } |
| | | |
| | | switch (histKey) |
| | | { |
| | | case ADD: |
| | | if (value != null) |
| | | else |
| | | { |
| | | attrInfo.add(value, cn); |
| | | if (!options.equals(lastOptions)) |
| | | { |
| | | attrInfo = AttributeInfo.createAttributeInfo(attrType); |
| | | attrInfoWithOptions.put(options, attrInfo); |
| | | lastOptions = options; |
| | | } |
| | | } |
| | | break; |
| | | |
| | | case DEL: |
| | | if (value != null) |
| | | { |
| | | attrInfo.delete(value, cn); |
| | | } |
| | | break; |
| | | |
| | | case REPL: |
| | | attrInfo.delete(cn); |
| | | if (value != null) |
| | | { |
| | | attrInfo.add(value, cn); |
| | | } |
| | | break; |
| | | |
| | | case DELATTR: |
| | | attrInfo.delete(cn); |
| | | break; |
| | | attrInfo.load(histKey, value, cn); |
| | | } |
| | | } |
| | | } catch (Exception e) |
| | | { |
| | | // Any exception happening here means that the coding of the hsitorical |
| | | // information was wrong. |
| | | // Log an error and continue with an empty historical. |
| | | int msgID = MSGID_BAD_HISTORICAL; |
| | | String message = getMessage(msgID, entry.getDN().toString()); |
| | | logError(ErrorLogCategory.SYNCHRONIZATION, |
| | | ErrorLogSeverity.SEVERE_ERROR, |
| | | message, msgID); |
| | | } |
| | | |
| | | /* set the reference to the historical information in the entry */ |