001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.replication.plugin; 028 029import static org.opends.messages.ReplicationMessages.*; 030import static org.opends.server.replication.plugin.HistAttrModificationKey.*; 031 032import java.util.*; 033 034import org.forgerock.i18n.slf4j.LocalizedLogger; 035import org.forgerock.opendj.ldap.ByteString; 036import org.forgerock.opendj.ldap.ModificationType; 037import org.opends.server.core.DirectoryServer; 038import org.opends.server.replication.common.CSN; 039import org.opends.server.replication.protocol.OperationContext; 040import org.opends.server.types.*; 041import org.opends.server.types.operation.PreOperationAddOperation; 042import org.opends.server.types.operation.PreOperationModifyDNOperation; 043import org.opends.server.types.operation.PreOperationModifyOperation; 044import org.opends.server.util.TimeThread; 045 046/** 047 * This class is used to store historical information that is used to resolve modify conflicts 048 * <p> 049 * It is assumed that the common case is not to have conflict and therefore is optimized (in order 050 * of importance) for: 051 * <ol> 052 * <li>detecting potential conflict</li> 053 * <li>fast update of historical information for non-conflicting change</li> 054 * <li>fast and efficient purge</li> 055 * <li>compact</li> 056 * <li>solve conflict. This should also be as fast as possible but not at the cost of any of the 057 * other previous objectives</li> 058 * </ol> 059 * One Historical object is created for each entry in the entry cache each Historical Object 060 * contains a list of attribute historical information 061 */ 062public class EntryHistorical 063{ 064 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 065 066 /** Name of the attribute used to store historical information. */ 067 public static final String HISTORICAL_ATTRIBUTE_NAME = "ds-sync-hist"; 068 /** 069 * Name used to store attachment of historical information in the 070 * operation. This attachment allows to use in several different places 071 * the historical while reading/writing ONCE it from/to the entry. 072 */ 073 public static final String HISTORICAL = "ds-synch-historical"; 074 /** Name of the entryuuid attribute. */ 075 public static final String ENTRYUUID_ATTRIBUTE_NAME = "entryuuid"; 076 077 /** 078 * The delay to purge the historical information. 079 * <p> 080 * This delay indicates the time the domain keeps the historical information 081 * necessary to solve conflicts. When a change stored in the historical part 082 * of the user entry has a date (from its replication CSN) older than this 083 * delay, it is candidate to be purged. The purge is triggered on 2 events: 084 * modify of the entry, dedicated purge task. The purge is done when the 085 * historical is encoded. 086 */ 087 private long purgeDelayInMillisec = -1; 088 089 /** 090 * The oldest CSN stored in this entry historical attribute. 091 * null when this historical object has been created from 092 * an entry that has no historical attribute and after the last 093 * historical has been purged. 094 */ 095 private CSN oldestCSN; 096 097 /** 098 * For stats/monitoring purpose, the number of historical values 099 * purged the last time a purge has been applied on this entry historical. 100 */ 101 private int lastPurgedValuesCount; 102 103 /** The date when the entry was added. */ 104 private CSN entryADDDate; 105 /** The date when the entry was last renamed. */ 106 private CSN entryMODDNDate; 107 108 /** Contains Historical information for each attribute description. */ 109 private final Map<AttributeDescription, AttrHistorical> attributesHistorical = new HashMap<>(); 110 111 @Override 112 public String toString() 113 { 114 StringBuilder builder = new StringBuilder(); 115 builder.append(encodeAndPurge()); 116 return builder.toString(); 117 } 118 119 /** 120 * Process an operation. 121 * This method is responsible for detecting and resolving conflict for 122 * modifyOperation. This is done by using the historical information. 123 * 124 * @param modifyOperation the operation to be processed 125 * @param modifiedEntry the entry that is being modified (before modification) 126 * @return true if the replayed operation was in conflict 127 */ 128 public boolean replayOperation(PreOperationModifyOperation modifyOperation, Entry modifiedEntry) 129 { 130 boolean bConflict = false; 131 List<Modification> mods = modifyOperation.getModifications(); 132 CSN modOpCSN = OperationContext.getCSN(modifyOperation); 133 134 for (Iterator<Modification> it = mods.iterator(); it.hasNext(); ) 135 { 136 Modification m = it.next(); 137 138 // Read or create the attr historical for the attribute type and option 139 // contained in the mod 140 AttrHistorical attrHist = getOrCreateAttrHistorical(m); 141 if (attrHist.replayOperation(it, modOpCSN, modifiedEntry, m)) 142 { 143 bConflict = true; 144 } 145 } 146 147 return bConflict; 148 } 149 150 /** 151 * Update the historical information for the provided operation. 152 * <p> 153 * Steps: 154 * <ul> 155 * <li>compute the historical attribute</li> 156 * <li>update the mods in the provided operation by adding the update of the 157 * historical attribute</li> 158 * <li>update the modifiedEntry, already computed by core since we are in the 159 * preOperation plugin, that is called just before committing into the DB. 160 * </li> 161 * </ul> 162 * </p> 163 * 164 * @param modifyOperation 165 * the modification. 166 */ 167 public void setHistoricalAttrToOperation(PreOperationModifyOperation modifyOperation) 168 { 169 List<Modification> mods = modifyOperation.getModifications(); 170 Entry modifiedEntry = modifyOperation.getModifiedEntry(); 171 CSN csn = OperationContext.getCSN(modifyOperation); 172 173 /* 174 * If this is a local operation we need : 175 * - first to update the historical information, 176 * - then update the entry with the historical information 177 * If this is a replicated operation the historical information has 178 * already been set in the resolveConflict phase and we only need 179 * to update the entry 180 */ 181 if (!modifyOperation.isSynchronizationOperation()) 182 { 183 for (Modification mod : mods) 184 { 185 // Get the current historical for this attributeType/options 186 // (eventually read from the provided modification) 187 AttrHistorical attrHist = getOrCreateAttrHistorical(mod); 188 if (attrHist != null) 189 { 190 attrHist.processLocalOrNonConflictModification(csn, mod); 191 } 192 } 193 } 194 195 // Now do the 2 updates required by the core to be consistent: 196 // 197 // - add the modification of the ds-sync-hist attribute, 198 // to the current modifications of the MOD operation 199 Attribute attr = encodeAndPurge(); 200 mods.add(new Modification(ModificationType.REPLACE, attr)); 201 // - update the already modified entry 202 modifiedEntry.replaceAttribute(attr); 203 } 204 205 /** 206 * For a MODDN operation, add new or update existing historical information. 207 * <p> 208 * This method is NOT static because it relies on this Historical object created in the 209 * HandleConflictResolution phase. 210 * 211 * @param modifyDNOperation 212 * the modification for which the historical information should be created. 213 */ 214 public void setHistoricalAttrToOperation(PreOperationModifyDNOperation modifyDNOperation) 215 { 216 // Update this historical information with the operation CSN. 217 this.entryMODDNDate = OperationContext.getCSN(modifyDNOperation); 218 219 // Update the operations mods and the modified entry so that the 220 // historical information gets stored in the DB and indexed accordingly. 221 Entry modifiedEntry = modifyDNOperation.getUpdatedEntry(); 222 List<Modification> mods = modifyDNOperation.getModifications(); 223 224 Attribute attr = encodeAndPurge(); 225 226 // Now do the 2 updates required by the core to be consistent: 227 // 228 // - add the modification of the ds-sync-hist attribute, 229 // to the current modifications of the operation 230 mods.add(new Modification(ModificationType.REPLACE, attr)); 231 // - update the already modified entry 232 modifiedEntry.removeAttribute(attr.getAttributeType()); 233 modifiedEntry.addAttribute(attr, null); 234 } 235 236 /** 237 * Generate an attribute containing the historical information 238 * from the replication context attached to the provided operation 239 * and set this attribute in the operation. 240 * 241 * For ADD, the historical is made of the CSN read from the 242 * synchronization context attached to the operation. 243 * 244 * Called for both local and synchronization ADD preOperation. 245 * 246 * This historical information will be used to generate fake operation 247 * in case a Directory Server can not find a Replication Server with 248 * all its changes at connection time. 249 * This should only happen if a Directory Server or a Replication Server 250 * crashes. 251 * 252 * This method is static because there is no Historical object creation 253 * required here or before(in the HandleConflictResolution phase) 254 * 255 * @param addOperation The Operation to which the historical attribute will be added. 256 */ 257 public static void setHistoricalAttrToOperation(PreOperationAddOperation addOperation) 258 { 259 AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(HISTORICAL_ATTRIBUTE_NAME); 260 String attrValue = encodeHistorical(OperationContext.getCSN(addOperation), "add"); 261 List<Attribute> attrs = Attributes.createAsList(attrType, attrValue); 262 addOperation.setAttribute(attrType, attrs); 263 } 264 265 /** 266 * Builds an attributeValue for the supplied historical information and 267 * operation type . For ADD Operation : "dn:changeNumber:add", for MODDN 268 * Operation : "dn:changeNumber:moddn", etc. 269 * 270 * @param csn 271 * The date when the ADD Operation happened. 272 * @param operationType 273 * the operation type to encode 274 * @return The attribute value containing the historical information for the Operation type. 275 */ 276 private static String encodeHistorical(CSN csn, String operationType) 277 { 278 return "dn:" + csn + ":" + operationType; 279 } 280 281 /** 282 * Return an AttributeHistorical corresponding to the attribute type 283 * and options contained in the provided mod, 284 * The attributeHistorical is : 285 * - either read from this EntryHistorical object if one exist, 286 * - or created empty. 287 * Should never return null. 288 * 289 * @param mod the provided mod from which we'll use attributeType 290 * and options to retrieve/create the attribute historical 291 * @return the attribute historical retrieved or created empty. 292 */ 293 private AttrHistorical getOrCreateAttrHistorical(Modification mod) 294 { 295 // Read the provided mod 296 Attribute modAttr = mod.getAttribute(); 297 if (isHistoricalAttribute(modAttr)) 298 { 299 // Don't keep historical information for the attribute that is 300 // used to store the historical information. 301 return null; 302 } 303 304 // Read from this entryHistorical, 305 // Create one empty if none was existing in this entryHistorical. 306 AttributeDescription attrDesc = AttributeDescription.create(modAttr); 307 AttrHistorical attrHist = attributesHistorical.get(attrDesc); 308 if (attrHist == null) 309 { 310 attrHist = AttrHistorical.createAttributeHistorical(modAttr.getAttributeType()); 311 attributesHistorical.put(attrDesc, attrHist); 312 } 313 return attrHist; 314 } 315 316 /** 317 * For stats/monitoring purpose, returns the number of historical values 318 * purged the last time a purge has been applied on this entry historical. 319 * 320 * @return the purged values count. 321 */ 322 public int getLastPurgedValuesCount() 323 { 324 return this.lastPurgedValuesCount; 325 } 326 327 /** 328 * Encode this historical information object in an operational attribute and 329 * purge it from the values older than the purge delay. 330 * 331 * @return The historical information encoded in an operational attribute. 332 * @see HistoricalAttributeValue#HistoricalAttributeValue(String) the decode 333 * operation in HistoricalAttributeValue 334 */ 335 public Attribute encodeAndPurge() 336 { 337 long purgeDate = 0; 338 339 // Set the stats counter to 0 and compute the purgeDate to now minus 340 // the potentially set purge delay. 341 this.lastPurgedValuesCount = 0; 342 if (purgeDelayInMillisec>0) 343 { 344 purgeDate = TimeThread.getTime() - purgeDelayInMillisec; 345 } 346 347 AttributeType historicalAttrType = DirectoryServer.getAttributeTypeOrNull(HISTORICAL_ATTRIBUTE_NAME); 348 AttributeBuilder builder = new AttributeBuilder(historicalAttrType); 349 350 for (Map.Entry<AttributeDescription, AttrHistorical> mapEntry : attributesHistorical.entrySet()) 351 { 352 AttributeDescription attrDesc = mapEntry.getKey(); 353 String options = attrDesc.toString(); 354 AttrHistorical attrHist = mapEntry.getValue(); 355 356 CSN deleteTime = attrHist.getDeleteTime(); 357 /* generate the historical information for deleted attributes */ 358 boolean attrDel = deleteTime != null; 359 360 for (AttrValueHistorical attrValHist : attrHist.getValuesHistorical()) 361 { 362 final ByteString value = attrValHist.getAttributeValue(); 363 364 // Encode an attribute value 365 if (attrValHist.getValueDeleteTime() != null) 366 { 367 if (needsPurge(attrValHist.getValueDeleteTime(), purgeDate)) 368 { 369 // this hist must be purged now, so skip its encoding 370 continue; 371 } 372 String strValue = encode(DEL, options, attrValHist.getValueDeleteTime(), value); 373 builder.add(strValue); 374 } 375 else if (attrValHist.getValueUpdateTime() != null) 376 { 377 if (needsPurge(attrValHist.getValueUpdateTime(), purgeDate)) 378 { 379 // this hist must be purged now, so skip its encoding 380 continue; 381 } 382 383 String strValue; 384 final CSN updateTime = attrValHist.getValueUpdateTime(); 385 // FIXME very suspicious use of == in the next if statement, 386 // unit tests do not like changing it 387 if (attrDel && updateTime == deleteTime && value != null) 388 { 389 strValue = encode(REPL, options, updateTime, value); 390 attrDel = false; 391 } 392 else if (value != null) 393 { 394 strValue = encode(ADD, options, updateTime, value); 395 } 396 else 397 { 398 // "add" without any value is suspicious. Tests never go there. 399 // Is this used to encode "add" with an empty string? 400 strValue = encode(ADD, options, updateTime); 401 } 402 403 builder.add(strValue); 404 } 405 } 406 407 if (attrDel) 408 { 409 if (needsPurge(deleteTime, purgeDate)) 410 { 411 // this hist must be purged now, so skip its encoding 412 continue; 413 } 414 builder.add(encode(ATTRDEL, options, deleteTime)); 415 } 416 } 417 418 if (entryADDDate != null && !needsPurge(entryADDDate, purgeDate)) 419 { 420 // Encode the historical information for the ADD Operation. 421 // Stores the ADDDate when not older than the purge delay 422 builder.add(encodeHistorical(entryADDDate, "add")); 423 } 424 425 if (entryMODDNDate != null && !needsPurge(entryMODDNDate, purgeDate)) 426 { 427 // Encode the historical information for the MODDN Operation. 428 // Stores the MODDNDate when not older than the purge delay 429 builder.add(encodeHistorical(entryMODDNDate, "moddn")); 430 } 431 432 return builder.toAttribute(); 433 } 434 435 private boolean needsPurge(CSN csn, long purgeDate) 436 { 437 boolean needsPurge = purgeDelayInMillisec > 0 && csn.getTime() <= purgeDate; 438 if (needsPurge) 439 { 440 // this hist must be purged now, because older than the purge delay 441 this.lastPurgedValuesCount++; 442 } 443 return needsPurge; 444 } 445 446 private String encode(HistAttrModificationKey modKey, String options, CSN changeTime) 447 { 448 return options + ":" + changeTime + ":" + modKey; 449 } 450 451 private String encode(HistAttrModificationKey modKey, String options, CSN changeTime, ByteString value) 452 { 453 return options + ":" + changeTime + ":" + modKey + ":" + value; 454 } 455 456 /** 457 * Set the delay to purge the historical information. The purge is applied 458 * only when historical attribute is updated (write operations). 459 * 460 * @param purgeDelay the purge delay in ms 461 */ 462 public void setPurgeDelay(long purgeDelay) 463 { 464 this.purgeDelayInMillisec = purgeDelay; 465 } 466 467 /** 468 * Indicates if the Entry was renamed or added after the CSN that is given as 469 * a parameter. 470 * 471 * @param csn 472 * The CSN with which the ADD or Rename date must be compared. 473 * @return A boolean indicating if the Entry was renamed or added after the 474 * CSN that is given as a parameter. 475 */ 476 public boolean addedOrRenamedAfter(CSN csn) 477 { 478 return csn.isOlderThan(entryADDDate) || csn.isOlderThan(entryMODDNDate); 479 } 480 481 /** 482 * Returns the lastCSN when the entry DN was modified. 483 * 484 * @return The lastCSN when the entry DN was modified. 485 */ 486 public CSN getDNDate() 487 { 488 if (entryADDDate == null) 489 { 490 return entryMODDNDate; 491 } 492 if (entryMODDNDate == null) 493 { 494 return entryADDDate; 495 } 496 497 if (entryMODDNDate.isOlderThan(entryADDDate)) 498 { 499 return entryMODDNDate; 500 } 501 else 502 { 503 return entryADDDate; 504 } 505 } 506 507 /** 508 * Construct an Historical object from the provided entry by reading the historical attribute. 509 * Return an empty object when the entry does not contain any historical attribute. 510 * 511 * @param entry The entry which historical information must be loaded 512 * @return The constructed Historical information object 513 */ 514 public static EntryHistorical newInstanceFromEntry(Entry entry) 515 { 516 // Read the DB historical attribute from the entry 517 List<Attribute> histAttrWithOptionsFromEntry = getHistoricalAttr(entry); 518 519 // Now we'll build the Historical object we want to construct 520 final EntryHistorical newHistorical = new EntryHistorical(); 521 if (histAttrWithOptionsFromEntry == null) 522 { 523 // No historical attribute in the entry, return empty object 524 return newHistorical; 525 } 526 527 try 528 { 529 // For each value of the historical attr read (mod. on a user attribute) 530 // build an AttrInfo sub-object 531 532 // Traverse the Attributes (when several options for the hist attr) 533 // of the historical attribute read from the entry 534 for (Attribute histAttrFromEntry : histAttrWithOptionsFromEntry) 535 { 536 // For each Attribute (option), traverse the values 537 for (ByteString histAttrValueFromEntry : histAttrFromEntry) 538 { 539 // From each value of the hist attr, create an object 540 final HistoricalAttributeValue histVal = new HistoricalAttributeValue(histAttrValueFromEntry.toString()); 541 final CSN csn = histVal.getCSN(); 542 543 // update the oldest CSN stored in the new entry historical 544 newHistorical.updateOldestCSN(csn); 545 546 if (histVal.isADDOperation()) 547 { 548 newHistorical.entryADDDate = csn; 549 } 550 else if (histVal.isMODDNOperation()) 551 { 552 newHistorical.entryMODDNDate = csn; 553 } 554 else 555 { 556 AttributeDescription attrDesc = histVal.getAttributeDescription(); 557 if (attrDesc == null) 558 { 559 /* 560 * This attribute is unknown from the schema 561 * Just skip it, the modification will be processed but no 562 * historical information is going to be kept. 563 * Log information for the repair tool. 564 */ 565 logger.error(ERR_UNKNOWN_ATTRIBUTE_IN_HISTORICAL, entry.getName(), histVal.getAttrString()); 566 continue; 567 } 568 569 /* if attribute type does not match we create new 570 * AttrInfoWithOptions and AttrInfo 571 * we also add old AttrInfoWithOptions into histObj.attributesInfo 572 * if attribute type match but options does not match we create new 573 * AttrInfo that we add to AttrInfoWithOptions 574 * if both match we keep everything 575 */ 576 AttrHistorical attrInfo = newHistorical.attributesHistorical.get(attrDesc); 577 if (attrInfo == null) 578 { 579 attrInfo = AttrHistorical.createAttributeHistorical(attrDesc.getAttributeType()); 580 newHistorical.attributesHistorical.put(attrDesc, attrInfo); 581 } 582 attrInfo.assign(histVal.getHistKey(), histVal.getAttributeValue(), csn); 583 } 584 } 585 } 586 } catch (Exception e) 587 { 588 // Any exception happening here means that the coding of the historical 589 // information was wrong. 590 // Log an error and continue with an empty historical. 591 logger.error(ERR_BAD_HISTORICAL, entry.getName()); 592 } 593 594 /* set the reference to the historical information in the entry */ 595 return newHistorical; 596 } 597 598 /** 599 * Use this historical information to generate fake operations that would 600 * result in this historical information. 601 * TODO : This is only implemented for MODIFY, MODRDN and ADD 602 * need to complete with DELETE. 603 * @param entry The Entry to use to generate the FakeOperation Iterable. 604 * 605 * @return an Iterable of FakeOperation that would result in this historical information. 606 */ 607 public static Iterable<FakeOperation> generateFakeOperations(Entry entry) 608 { 609 TreeMap<CSN, FakeOperation> operations = new TreeMap<>(); 610 List<Attribute> attrs = getHistoricalAttr(entry); 611 if (attrs != null) 612 { 613 for (Attribute attr : attrs) 614 { 615 for (ByteString val : attr) 616 { 617 HistoricalAttributeValue histVal = new HistoricalAttributeValue(val.toString()); 618 if (histVal.isADDOperation()) 619 { 620 // Found some historical information indicating that this entry was just added. 621 // Create the corresponding ADD operation. 622 operations.put(histVal.getCSN(), new FakeAddOperation(histVal.getCSN(), entry)); 623 } 624 else if (histVal.isMODDNOperation()) 625 { 626 // Found some historical information indicating that this entry was just renamed. 627 // Create the corresponding ADD operation. 628 operations.put(histVal.getCSN(), new FakeModdnOperation(histVal.getCSN(), entry)); 629 } 630 else 631 { 632 // Found some historical information for modify operation. 633 // Generate the corresponding ModifyOperation or update 634 // the already generated Operation if it can be found. 635 CSN csn = histVal.getCSN(); 636 Modification mod = histVal.generateMod(); 637 FakeOperation fakeOperation = operations.get(csn); 638 639 if (fakeOperation instanceof FakeModifyOperation) 640 { 641 FakeModifyOperation modifyFakeOperation = (FakeModifyOperation) fakeOperation; 642 modifyFakeOperation.addModification(mod); 643 } 644 else 645 { 646 String uuidString = getEntryUUID(entry); 647 FakeModifyOperation modifyFakeOperation = 648 new FakeModifyOperation(entry.getName(), csn, uuidString); 649 modifyFakeOperation.addModification(mod); 650 operations.put(histVal.getCSN(), modifyFakeOperation); 651 } 652 } 653 } 654 } 655 } 656 return operations.values(); 657 } 658 659 /** 660 * Get the attribute used to store the historical information from the provided Entry. 661 * 662 * @param entry The entry containing the historical information. 663 * @return The Attribute used to store the historical information. 664 * Several values on the list if several options for this attribute. 665 * Null if not present. 666 */ 667 public static List<Attribute> getHistoricalAttr(Entry entry) 668 { 669 return entry.getAttribute(HISTORICAL_ATTRIBUTE_NAME); 670 } 671 672 /** 673 * Get the entry unique Id in String form. 674 * 675 * @param entry The entry for which the unique id should be returned. 676 * @return The Unique Id of the entry, or a fake one if none is found. 677 */ 678 public static String getEntryUUID(Entry entry) 679 { 680 AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(ENTRYUUID_ATTRIBUTE_NAME); 681 List<Attribute> uuidAttrs = entry.getOperationalAttribute(attrType); 682 return extractEntryUUID(uuidAttrs, entry.getName()); 683 } 684 685 /** 686 * Get the Entry Unique Id from an add operation. 687 * This must be called after the entry uuid pre-op plugin (i.e no 688 * sooner than the replication provider pre-op) 689 * 690 * @param op The operation 691 * @return The Entry Unique Id String form. 692 */ 693 public static String getEntryUUID(PreOperationAddOperation op) 694 { 695 AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(ENTRYUUID_ATTRIBUTE_NAME); 696 List<Attribute> uuidAttrs = op.getOperationalAttributes().get(attrType); 697 return extractEntryUUID(uuidAttrs, op.getEntryDN()); 698 } 699 700 /** 701 * Check if a given attribute is an attribute used to store historical 702 * information. 703 * 704 * @param attr The attribute that needs to be checked. 705 * 706 * @return a boolean indicating if the given attribute is 707 * used to store historical information. 708 */ 709 public static boolean isHistoricalAttribute(Attribute attr) 710 { 711 AttributeType attrType = attr.getAttributeType(); 712 return HISTORICAL_ATTRIBUTE_NAME.equals(attrType.getNameOrOID()); 713 } 714 715 /** 716 * Potentially update the oldest CSN stored in this entry historical 717 * with the provided CSN when its older than the current oldest. 718 * 719 * @param csn the provided CSN. 720 */ 721 private void updateOldestCSN(CSN csn) 722 { 723 if (csn != null 724 && (this.oldestCSN == null || csn.isOlderThan(this.oldestCSN))) 725 { 726 this.oldestCSN = csn; 727 } 728 } 729 730 /** 731 * Returns the oldest CSN stored in this entry historical attribute. 732 * 733 * @return the oldest CSN stored in this entry historical attribute. 734 * Returns null when this historical object has been created from 735 * an entry that has no historical attribute and after the last 736 * historical has been purged. 737 */ 738 public CSN getOldestCSN() 739 { 740 return this.oldestCSN; 741 } 742 743 /** 744 * Extracts the entryUUID attribute value from the provided list of 745 * attributes. If the attribute is not present one is generated from the DN 746 * using the same algorithm as the entryUUID virtual attribute provider. 747 */ 748 private static String extractEntryUUID(List<Attribute> entryUUIDAttributes, DN entryDN) 749 { 750 if (entryUUIDAttributes != null) 751 { 752 Attribute uuidAttr = entryUUIDAttributes.get(0); 753 if (!uuidAttr.isEmpty()) 754 { 755 return uuidAttr.iterator().next().toString(); 756 } 757 } 758 759 // Generate a fake entryUUID: see OPENDJ-181. In rare pathological cases 760 // an entryUUID attribute may not be present and this causes severe side effects 761 // for replication which requires the attribute to always be present 762 if (logger.isTraceEnabled()) 763 { 764 logger.trace( 765 "Replication requires an entryUUID attribute in order " 766 + "to perform conflict resolution, but none was " 767 + "found in entry \"%s\": generating virtual entryUUID instead", 768 entryDN); 769 } 770 771 return UUID.nameUUIDFromBytes(entryDN.toNormalizedByteString().toByteArray()).toString(); 772 } 773}