/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2013 ForgeRock AS */ package org.opends.server.replication.plugin; import static org.opends.messages.ReplicationMessages.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.getBytes; import java.util.*; import org.opends.messages.Message; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.replication.common.ChangeNumber; import org.opends.server.replication.protocol.OperationContext; import org.opends.server.types.*; import org.opends.server.types.operation.PreOperationAddOperation; import org.opends.server.types.operation.PreOperationModifyDNOperation; import org.opends.server.types.operation.PreOperationModifyOperation; import org.opends.server.util.TimeThread; /** * This class is used to store historical information that is * used to resolve modify conflicts * * It is assumed that the common case is not to have conflict and * therefore is optimized (in order of importance) for : * 1- detecting potential conflict * 2- fast update of historical information for non-conflicting change * 3- fast and efficient purge * 4- compact * 5- solve conflict. This should also be as fast as possible but * not at the cost of any of the other previous objectives * * One Historical object is created for each entry in the entry cache * each Historical Object contains a list of attribute historical information */ public class EntryHistorical { /** * Name of the attribute used to store historical information. */ public static final String HISTORICAL_ATTRIBUTE_NAME = "ds-sync-hist"; /** * Name used to store attachment of historical information in the * operation. This attachement allows to use in several different places * the historical while reading/writing ONCE it from/to the entry. */ public static final String HISTORICAL = "ds-synch-historical"; /** * Name of the entryuuid attribute. */ public static final String ENTRYUUID_ATTRIBUTE_NAME = "entryuuid"; /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /* The delay to purge the historical informations * This delay indicates the time the domain keeps the historical * information necessary to solve conflicts.When a change stored in the * historical part of the user entry has a date (from its replication * ChangeNumber) older than this delay, it is candidate to be purged. * The purge is triggered on 2 events: modify of the entry, dedicated purge * task. * * The purge is done when the historical is encoded. */ private long purgeDelayInMillisec = -1; /* * The oldest ChangeNumber stored in this entry historical attribute. * null when this historical object has been created from * an entry that has no historical attribute and after the last * historical has been purged. */ private ChangeNumber oldestChangeNumber = null; /** * For stats/monitoring purpose, the number of historical values * purged the last time a purge has been applied on this entry historical. */ private int lastPurgedValuesCount = 0; /** * The in-memory historical information is made of. * * EntryHistorical ::= ADDDate MODDNDate attributesInfo * ADDDate ::= ChangeNumber // the date the entry was added * MODDNDate ::= ChangeNumber // the date the entry was last renamed * * attributesInfo ::= (AttrInfoWithOptions)* * one AttrInfoWithOptions by attributeType * * AttrInfoWithOptions ::= (AttributeInfo)* * one AttributeInfo by attributeType and option * * AttributeInfo ::= AttrInfoSingle | AttrInfoMultiple * * AttrInfoSingle ::= AddTime DeleteTime ValueInfo * * AttrInfoMultiple ::= AddTime DeleteTime ValuesInfo * * ValuesInfo ::= (AttrValueHistorical)* * AttrValueHistorical is the historical of the * the modification of one value * * AddTime ::= ChangeNumber // last time the attribute was added * // to the entry * DeleteTime ::= ChangeNumber // last time the attribute was deleted * // from the entry * * AttrValueHistorical ::= AttributeValue valueDeleteTime valueUpdateTime * valueDeleteTime ::= ChangeNumber * valueUpdateTime ::= ChangeNumber * * - a list indexed on AttributeType of AttrInfoWithOptions : * each value is the historical for this attribute * an AttrInfoWithOptions is a set indexed on the optionValue(string) of * AttributeInfo * */ // The date when the entry was added. private ChangeNumber entryADDDate = null; // The date when the entry was last renamed. private ChangeNumber entryMODDNDate = null; // contains Historical information for each attribute sorted by attribute type // key:AttributeType value:AttrInfoWithOptions private HashMap<AttributeType,AttrHistoricalWithOptions> attributesHistorical = new HashMap<AttributeType,AttrHistoricalWithOptions>(); /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(encodeAndPurge()); return builder.toString(); } /** * Process an operation. * This method is responsible for detecting and resolving conflict for * modifyOperation. This is done by using the historical information. * * @param modifyOperation the operation to be processed * @param modifiedEntry the entry that is being modified (before modification) * @return true if the replayed operation was in conflict */ public boolean replayOperation(PreOperationModifyOperation modifyOperation, Entry modifiedEntry) { boolean bConflict = false; List<Modification> mods = modifyOperation.getModifications(); ChangeNumber modOpChangeNumber = OperationContext.getChangeNumber(modifyOperation); for (Iterator<Modification> modsIterator = mods.iterator(); modsIterator.hasNext(); ) { // Traverse the mods of this MOD operation Modification m = modsIterator.next(); // Read or create the attr historical for the attribute type and option // contained in the mod AttrHistorical attrHist = getOrCreateAttrHistorical(m); if (attrHist.replayOperation(modsIterator, modOpChangeNumber, modifiedEntry, m)) { bConflict = true; } } return bConflict; } /** * Update the historical information for the provided operation. * Steps : * - compute the historical attribute * - update the mods in the provided operation by adding the update of the * historical attribute * - update the modifiedEntry, already computed by core since we are in the * preOperation plugin, that is called just before commiting into the DB. * * @param modifyOperation the modification. */ public void setHistoricalAttrToOperation( PreOperationModifyOperation modifyOperation) { List<Modification> mods = modifyOperation.getModifications(); Entry modifiedEntry = modifyOperation.getModifiedEntry(); ChangeNumber changeNumber = OperationContext.getChangeNumber(modifyOperation); /* * If this is a local operation we need : * - first to update the historical information, * - then update the entry with the historical information * If this is a replicated operation the historical information has * already been set in the resolveConflict phase and we only need * to update the entry */ if (!modifyOperation.isSynchronizationOperation()) { for (Modification mod : mods) { // Get the current historical for this attributeType/options // (eventually read from the provided modification) AttrHistorical attrHist = getOrCreateAttrHistorical(mod); if (attrHist != null) attrHist.processLocalOrNonConflictModification(changeNumber, mod); } } // Now do the 2 updates required by the core to be consistent: // // - add the modification of the ds-sync-hist attribute, // to the current modifications of the MOD operation Attribute attr = encodeAndPurge(); Modification mod = new Modification(ModificationType.REPLACE, attr); mods.add(mod); // - update the already modified entry modifiedEntry.replaceAttribute(attr); } /** * For a MODDN operation, add new or update existing historical information. * * This method is NOT static because it relies on this Historical object * created in the HandleConflictResolution phase. * * @param modifyDNOperation the modification for which the historical * information should be created. */ public void setHistoricalAttrToOperation( PreOperationModifyDNOperation modifyDNOperation) { // Update this historical information with the operation ChangeNumber. this.entryMODDNDate = OperationContext.getChangeNumber(modifyDNOperation); // Update the operations mods and the modified entry so that the // historical information gets stored in the DB and indexed accordingly. Entry modifiedEntry = modifyDNOperation.getUpdatedEntry(); List<Modification> mods = modifyDNOperation.getModifications(); Attribute attr = encodeAndPurge(); // Now do the 2 updates required by the core to be consistent: // // - add the modification of the ds-sync-hist attribute, // to the current modifications of the operation Modification mod; mod = new Modification(ModificationType.REPLACE, attr); mods.add(mod); // - update the already modified entry modifiedEntry.removeAttribute(attr.getAttributeType()); modifiedEntry.addAttribute(attr, null); } /** * Generate an attribute containing the historical information * from the replication context attached to the provided operation * and set this attribute in the operation. * * For ADD, the historical is made of the changeNumber read from the * synchronization context attached to the operation. * * Called for both local and synchronization ADD preOperation. * * This historical information will be used to generate fake operation * in case a Directory Server can not find a Replication Server with * all its changes at connection time. * This should only happen if a Directory Server or a Replication Server * crashes. * * This method is static because there is no Historical object creation * required here or before(in the HandleConflictResolution phase) * * @param addOperation The Operation to which the historical attribute will * be added. */ public static void setHistoricalAttrToOperation( PreOperationAddOperation addOperation) { AttributeType historicalAttrType = DirectoryServer.getSchema().getAttributeType(HISTORICAL_ATTRIBUTE_NAME); // Get the changeNumber from the attached synchronization context // Create the attribute (encoded) Attribute attr = Attributes.create(historicalAttrType, encodeAddHistorical(OperationContext.getChangeNumber(addOperation))); // Set the created attribute to the operation List<Attribute> attrList = new LinkedList<Attribute>(); attrList.add(attr); addOperation.setAttribute(historicalAttrType, attrList); } /** * Encode in the returned attributeValue, this historical information for * an ADD operation. * For ADD Operation : "dn:changeNumber:add" * * @param cn The date when the ADD Operation happened. * @return The encoded attribute value containing the historical * information for the Operation. */ private static AttributeValue encodeAddHistorical(ChangeNumber cn) { AttributeType historicalAttrType = DirectoryServer.getSchema().getAttributeType(HISTORICAL_ATTRIBUTE_NAME); String strValue = "dn:" + cn.toString() +":add"; AttributeValue val = AttributeValues.create(historicalAttrType, strValue); return val; } /** * Encode in the returned attributeValue, the historical information for * a MODDN operation. * For MODDN Operation : "dn:changeNumber:moddn" * * @param cn The date when the MODDN Operation happened. * @return The encoded attribute value containing the historical * information for the Operation. */ private static AttributeValue encodeMODDNHistorical(ChangeNumber cn) { AttributeType historicalAttrType = DirectoryServer.getSchema().getAttributeType(HISTORICAL_ATTRIBUTE_NAME); String strValue = "dn:" + cn.toString() +":moddn"; AttributeValue val = AttributeValues.create(historicalAttrType, strValue); return val; } /** * Return an AttributeHistorical corresponding to the attribute type * and options contained in the provided mod, * The attributeHistorical is : * - either read from this EntryHistorical object if one exist, * - or created empty. * Should never return null. * * @param mod the provided mod from which we'll use attributeType * and options to retrieve/create the attribute historical * @return the attribute historical retrieved or created empty. */ private AttrHistorical getOrCreateAttrHistorical(Modification mod) { // Read the provided mod Attribute modAttr = mod.getAttribute(); if (isHistoricalAttribute(modAttr)) { // Don't keep historical information for the attribute that is // used to store the historical information. return null; } Set<String> modOptions = modAttr.getOptions(); AttributeType modAttrType = modAttr.getAttributeType(); // Read from this entryHistorical, // Create one empty if none was existing in this entryHistorical. AttrHistoricalWithOptions attrHistWithOptions = attributesHistorical.get(modAttrType); AttrHistorical attrHist; if (attrHistWithOptions != null) { attrHist = attrHistWithOptions.get(modOptions); } else { attrHistWithOptions = new AttrHistoricalWithOptions(); attributesHistorical.put(modAttrType, attrHistWithOptions); attrHist = null; } if (attrHist == null) { attrHist = AttrHistorical.createAttributeHistorical(modAttrType); attrHistWithOptions.put(modOptions, attrHist); } return attrHist; } /** * For stats/monitoring purpose, returns the number of historical values * purged the last time a purge has been applied on this entry historical. * * @return the purged values count. */ public int getLastPurgedValuesCount() { return this.lastPurgedValuesCount; } /** * Encode this historical information object in an operational attribute * and purge it from the values older than the purge delay. * * @return The historical information encoded in an operational attribute. */ public Attribute encodeAndPurge() { long purgeDate = 0; // Set the stats counter to 0 and compute the purgeDate to now minus // the potentially set purge delay. this.lastPurgedValuesCount = 0; if (purgeDelayInMillisec>0) purgeDate = TimeThread.getTime() - purgeDelayInMillisec; AttributeType historicalAttrType = DirectoryServer.getSchema().getAttributeType(HISTORICAL_ATTRIBUTE_NAME); AttributeBuilder builder = new AttributeBuilder(historicalAttrType); for (Map.Entry<AttributeType, AttrHistoricalWithOptions> entryWithOptions : attributesHistorical.entrySet()) { // Encode an attribute type AttributeType type = entryWithOptions.getKey(); HashMap<Set<String> , AttrHistorical> attrwithoptions = entryWithOptions.getValue().getAttributesInfo(); for (Map.Entry<Set<String>, AttrHistorical> entry : attrwithoptions.entrySet()) { // Encode an (attribute type/option) boolean delAttr = false; Set<String> options = entry.getKey(); String optionsString = ""; AttrHistorical attrHist = entry.getValue(); if (options != null) { StringBuilder optionsBuilder = new StringBuilder(); for (String s : options) { optionsBuilder.append(';'); optionsBuilder.append(s); } optionsString = optionsBuilder.toString(); } ChangeNumber deleteTime = attrHist.getDeleteTime(); /* generate the historical information for deleted attributes */ if (deleteTime != null) { delAttr = true; } for (AttrValueHistorical attrValHist : attrHist.getValuesHistorical() .keySet()) { // Encode an attribute value String strValue; if (attrValHist.getValueDeleteTime() != null) { // this hist must be purged now, so skip its encoding if ((purgeDelayInMillisec>0) && (attrValHist.getValueDeleteTime().getTime()<=purgeDate)) { this.lastPurgedValuesCount++; continue; } strValue = type.getNormalizedPrimaryName() + optionsString + ":" + attrValHist.getValueDeleteTime().toString() + ":del:" + attrValHist.getAttributeValue().toString(); AttributeValue val = AttributeValues.create(historicalAttrType, strValue); builder.add(val); } else if (attrValHist.getValueUpdateTime() != null) { if ((purgeDelayInMillisec>0) && (attrValHist.getValueUpdateTime().getTime()<=purgeDate)) { // this hist must be purged now, so skip its encoding this.lastPurgedValuesCount++; continue; } if ((delAttr && attrValHist.getValueUpdateTime() == deleteTime) && (attrValHist.getAttributeValue() != null)) { strValue = type.getNormalizedPrimaryName() + optionsString + ":" + attrValHist.getValueUpdateTime().toString() + ":repl:" + attrValHist.getAttributeValue().toString(); delAttr = false; } else { if (attrValHist.getAttributeValue() == null) { strValue = type.getNormalizedPrimaryName() + optionsString + ":" + attrValHist.getValueUpdateTime().toString() + ":add"; } else { strValue = type.getNormalizedPrimaryName() + optionsString + ":" + attrValHist.getValueUpdateTime().toString() + ":add:" + attrValHist.getAttributeValue().toString(); } } AttributeValue val = AttributeValues.create(historicalAttrType, strValue); builder.add(val); } } if (delAttr) { // Stores the attr deletion hist when not older than the purge delay if ((purgeDelayInMillisec>0) && (deleteTime.getTime()<=purgeDate)) { // this hist must be purged now, so skip its encoding this.lastPurgedValuesCount++; continue; } String strValue = type.getNormalizedPrimaryName() + optionsString + ":" + deleteTime.toString() + ":attrDel"; AttributeValue val = AttributeValues.create(historicalAttrType, strValue); builder.add(val); } } } // Encode the historical information for the ADD Operation. if (entryADDDate != null) { // Stores the ADDDate when not older than the purge delay if ((purgeDelayInMillisec>0) && (entryADDDate.getTime()<=purgeDate)) { this.lastPurgedValuesCount++; } else { builder.add(encodeAddHistorical(entryADDDate)); } } // Encode the historical information for the MODDN Operation. if (entryMODDNDate != null) { // Stores the MODDNDate when not older than the purge delay if ((purgeDelayInMillisec>0) && (entryMODDNDate.getTime()<=purgeDate)) { this.lastPurgedValuesCount++; } else { builder.add(encodeMODDNHistorical(entryMODDNDate)); } } return builder.toAttribute(); } /** * Set the delay to purge the historical informations. The purge is applied * only when historical attribute is updated (write operations). * * @param purgeDelay the purge delay in ms */ public void setPurgeDelay(long purgeDelay) { this.purgeDelayInMillisec = purgeDelay; } /** * Indicates if the Entry was renamed or added after the ChangeNumber * that is given as a parameter. * * @param cn The ChangeNumber with which the ADD or Rename date must be * compared. * * @return A boolean indicating if the Entry was renamed or added after * the ChangeNumber that is given as a parameter. */ public boolean addedOrRenamedAfter(ChangeNumber cn) { if (cn.older(entryADDDate) || cn.older(entryMODDNDate)) { return true; } else return false; } /** * Returns the lastChangeNumber when the entry DN was modified. * * @return The lastChangeNumber when the entry DN was modified. */ public ChangeNumber getDNDate() { if (entryADDDate == null) return entryMODDNDate; if (entryMODDNDate == null) return entryADDDate; if (entryMODDNDate.older(entryADDDate)) return entryMODDNDate; else return entryADDDate; } /** * Construct an Historical object from the provided entry by reading the * historical attribute. * Return an empty object when the entry does not contain any * historical attribute. * * @param entry The entry which historical information must be loaded * @return The constructed Historical information object * */ public static EntryHistorical newInstanceFromEntry(Entry entry) { AttributeType lastAttrType = null; Set<String> lastOptions = new HashSet<String>(); AttrHistorical attrInfo = null; AttrHistoricalWithOptions attrInfoWithOptions = null; // Read the DB historical attribute from the entry List<Attribute> histAttrWithOptionsFromEntry = getHistoricalAttr(entry); // Now we'll build the Historical object we want to construct EntryHistorical newHistorical = new EntryHistorical(); if (histAttrWithOptionsFromEntry == null) { // No historical attribute in the entry, return empty object return newHistorical; } try { // For each value of the historical attr read (mod. on a user attribute) // build an AttrInfo subobject // Traverse the Attributes (when several options for the hist attr) // of the historical attribute read from the entry for (Attribute histAttrFromEntry : histAttrWithOptionsFromEntry) { // For each Attribute (option), traverse the values for (AttributeValue histAttrValueFromEntry : histAttrFromEntry) { // From each value of the hist attr, create an object HistoricalAttributeValue histVal = new HistoricalAttributeValue( histAttrValueFromEntry.getValue().toString()); AttributeType attrType = histVal.getAttrType(); Set<String> options = histVal.getOptions(); ChangeNumber cn = histVal.getCn(); AttributeValue value = histVal.getAttributeValue(); HistAttrModificationKey histKey = histVal.getHistKey(); // update the oldest ChangeNumber stored in the new entry historical newHistorical.updateOldestCN(cn); if (histVal.isADDOperation()) { newHistorical.entryADDDate = cn; } else if (histVal.isMODDNOperation()) { newHistorical.entryMODDNDate = cn; } else { if (attrType == null) { /* * This attribute is unknown from the schema * Just skip it, the modification will be processed but no * historical information is going to be kept. * Log information for the repair tool. */ Message message = ERR_UNKNOWN_ATTRIBUTE_IN_HISTORICAL.get( entry.getDN().toNormalizedString(), histVal.getAttrString()); logError(message); 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 = AttrHistorical.createAttributeHistorical(attrType); // Create attrInfoWithOptions and store inside the attrInfo attrInfoWithOptions = new AttrHistoricalWithOptions(); attrInfoWithOptions.put(options, attrInfo); // Store this attrInfoWithOptions in the newHistorical object newHistorical.attributesHistorical. put(attrType, attrInfoWithOptions); lastAttrType = attrType; lastOptions = options; } else { if (!options.equals(lastOptions)) { attrInfo = AttrHistorical.createAttributeHistorical(attrType); attrInfoWithOptions.put(options, attrInfo); lastOptions = options; } } attrInfo.assign(histKey, value, cn); } } } } catch (Exception e) { // Any exception happening here means that the coding of the historical // information was wrong. // Log an error and continue with an empty historical. Message message = ERR_BAD_HISTORICAL.get(entry.getDN().toString()); logError(message); } /* set the reference to the historical information in the entry */ return newHistorical; } /** * Use this historical information to generate fake operations that would * result in this historical information. * TODO : This is only implemented for MODIFY, MODRDN and ADD * need to complete with DELETE. * @param entry The Entry to use to generate the FakeOperation Iterable. * * @return an Iterable of FakeOperation that would result in this historical * information. */ public static Iterable<FakeOperation> generateFakeOperations(Entry entry) { TreeMap<ChangeNumber, FakeOperation> operations = new TreeMap<ChangeNumber, FakeOperation>(); List<Attribute> attrs = getHistoricalAttr(entry); if (attrs != null) { for (Attribute attr : attrs) { for (AttributeValue val : attr) { HistoricalAttributeValue histVal = new HistoricalAttributeValue(val.getValue().toString()); if (histVal.isADDOperation()) { // Found some historical information indicating that this // entry was just added. // Create the corresponding ADD operation. operations.put(histVal.getCn(), new FakeAddOperation(histVal.getCn(), entry)); } else if (histVal.isMODDNOperation()) { // Found some historical information indicating that this // entry was just renamed. // Create the corresponding ADD operation. operations.put(histVal.getCn(), new FakeModdnOperation(histVal.getCn(), entry)); } else { // Found some historical information for modify operation. // Generate the corresponding ModifyOperation or update // the already generated Operation if it can be found. ChangeNumber cn = histVal.getCn(); Modification mod = histVal.generateMod(); FakeModifyOperation modifyFakeOperation; FakeOperation fakeOperation = operations.get(cn); if ((fakeOperation != null) && (fakeOperation instanceof FakeModifyOperation)) { modifyFakeOperation = (FakeModifyOperation) fakeOperation; modifyFakeOperation.addModification(mod); } else { String uuidString = getEntryUUID(entry); modifyFakeOperation = new FakeModifyOperation(entry.getDN(), cn, uuidString); modifyFakeOperation.addModification(mod); operations.put(histVal.getCn(), modifyFakeOperation); } } } } } return operations.values(); } /** * Get the attribute used to store the historical information from * the provided Entry. * * @param entry The entry containing the historical information. * * @return The Attribute used to store the historical information. * Several values on the list if several options for this attribute. * Null if not present. */ public static List<Attribute> getHistoricalAttr(Entry entry) { return entry.getAttribute(HISTORICAL_ATTRIBUTE_NAME); } /** * Get the entry unique Id in String form. * * @param entry The entry for which the unique id should be returned. * * @return The Unique Id of the entry, or a fake one if none is found. */ public static String getEntryUUID(Entry entry) { AttributeType entryuuidAttrType = DirectoryServer.getSchema().getAttributeType(ENTRYUUID_ATTRIBUTE_NAME); List<Attribute> uuidAttrs = entry.getOperationalAttribute(entryuuidAttrType); return extractEntryUUID(uuidAttrs, entry.getDN()); } /** * Get the Entry Unique Id from an add operation. * This must be called after the entry uuid preop plugin (i.e no * sooner than the replication provider pre-op) * * @param op The operation * @return The Entry Unique Id String form. */ public static String getEntryUUID(PreOperationAddOperation op) { Map<AttributeType, List<Attribute>> attrs = op.getOperationalAttributes(); AttributeType entryuuidAttrType = DirectoryServer.getSchema().getAttributeType(ENTRYUUID_ATTRIBUTE_NAME); List<Attribute> uuidAttrs = attrs.get(entryuuidAttrType); return extractEntryUUID(uuidAttrs, op.getEntryDN()); } /** * Check if a given attribute is an attribute used to store historical * information. * * @param attr The attribute that needs to be checked. * * @return a boolean indicating if the given attribute is * used to store historical information. */ public static boolean isHistoricalAttribute(Attribute attr) { AttributeType attrType = attr.getAttributeType(); return attrType.getNameOrOID().equals(EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); } /** * Potentially update the oldest ChangeNumber stored in this entry historical * with the provided ChangeNumber when its older than the current oldest. * * @param cn the provided ChangeNumber. */ private void updateOldestCN(ChangeNumber cn) { if (cn != null) { if (this.oldestChangeNumber == null) this.oldestChangeNumber = cn; else if (cn.older(this.oldestChangeNumber)) this.oldestChangeNumber = cn; } } /** * Returns the oldest ChangeNumber stored in this entry historical attribute. * * @return the oldest ChangeNumber stored in this entry historical attribute. * Returns null when this historical object has been created from * an entry that has no historical attribute and after the last * historical has been purged. */ public ChangeNumber getOldestCN() { return this.oldestChangeNumber; } // Extracts the entryUUID attribute value from the provided list of // attributes. If the attribute is not present one is generated from the DN // using the same algorithm as the entryUUID virtual attribute provider. private static String extractEntryUUID(List<Attribute> entryUUIDAttributes, DN entryDN) { if (entryUUIDAttributes != null) { Attribute uuid = entryUUIDAttributes.get(0); if (!uuid.isEmpty()) { AttributeValue uuidVal = uuid.iterator().next(); return uuidVal.getValue().toString(); } } // Generate a fake entryUUID: see OPENDJ-181. In rare pathological cases // an entryUUID attribute may not be present and this causes severe side // effects for replication which requires the attribute to always be // present. if (debugEnabled()) { TRACER.debugWarning( "Replication requires an entryUUID attribute in order " + "to perform conflict resolution, but none was " + "found in entry \"%s\": generating virtual entryUUID instead", entryDN); } String normDNString = entryDN.toNormalizedString(); return UUID.nameUUIDFromBytes(getBytes(normDNString)).toString(); } }