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 java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.Map; 032import java.util.Set; 033 034import org.forgerock.opendj.ldap.ByteString; 035import org.forgerock.opendj.ldap.ModificationType; 036import org.opends.server.replication.common.CSN; 037import org.opends.server.types.Attribute; 038import org.opends.server.types.AttributeBuilder; 039import org.opends.server.types.AttributeType; 040import org.opends.server.types.Entry; 041import org.opends.server.types.Modification; 042 043/** 044 * This class is used to store historical information for multiple valued attributes. 045 * One object of this type is created for each attribute that was changed in the entry. 046 * It allows to record the last time a given value was added,the last 047 * time a given value was deleted and the last time the whole attribute was deleted. 048 */ 049public class AttrHistoricalMultiple extends AttrHistorical 050{ 051 /** Last time when the attribute was deleted. */ 052 private CSN deleteTime; 053 /** Last time the attribute was modified. */ 054 private CSN lastUpdateTime; 055 /** 056 * Change history for the values of this attribute. 057 * <p> 058 * We are using a LinkedHashMap here because we want: 059 * <ol> 060 * <li>Fast access for removing/adding a AttrValueHistorical keyed by the attribute value => Use a Map</li> 061 * <li>Ordering changes according to the CSN of each changes => Use a LinkedHashMap</li> 062 * </ol> 063 */ 064 private final Map<AttrValueHistorical, AttrValueHistorical> valuesHist = new LinkedHashMap<>(); 065 066 /** 067 * Create a new object from the provided information. 068 * @param deleteTime the last time this attribute was deleted 069 * @param updateTime the last time this attribute was updated 070 * @param valuesHist the new attribute values when updated. 071 */ 072 public AttrHistoricalMultiple(CSN deleteTime, 073 CSN updateTime, 074 Map<AttrValueHistorical,AttrValueHistorical> valuesHist) 075 { 076 this.deleteTime = deleteTime; 077 this.lastUpdateTime = updateTime; 078 if (valuesHist != null) 079 { 080 this.valuesHist.putAll(valuesHist); 081 } 082 } 083 084 /** Creates a new object. */ 085 public AttrHistoricalMultiple() 086 { 087 this.deleteTime = null; 088 this.lastUpdateTime = null; 089 } 090 091 /** 092 * Returns the last time when the attribute was updated. 093 * @return the last time when the attribute was updated 094 */ 095 private CSN getLastUpdateTime() 096 { 097 return lastUpdateTime; 098 } 099 100 @Override 101 public CSN getDeleteTime() 102 { 103 return deleteTime; 104 } 105 106 /** 107 * Duplicate an object. CSNs are duplicated by references. 108 * <p> 109 * Method only called in tests 110 * 111 * @return the duplicated object. 112 */ 113 AttrHistoricalMultiple duplicate() 114 { 115 return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime, this.valuesHist); 116 } 117 118 /** 119 * Delete all historical information that is older than the provided CSN for 120 * this attribute type. 121 * Add the delete attribute state information 122 * @param csn time when the delete was done 123 */ 124 void delete(CSN csn) 125 { 126 // iterate through the values in the valuesInfo and suppress all the values 127 // that have not been added after the date of this delete. 128 Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator(); 129 while (it.hasNext()) 130 { 131 AttrValueHistorical info = it.next(); 132 if (csn.isNewerThanOrEqualTo(info.getValueUpdateTime()) && 133 csn.isNewerThanOrEqualTo(info.getValueDeleteTime())) 134 { 135 it.remove(); 136 } 137 } 138 139 if (csn.isNewerThan(deleteTime)) 140 { 141 deleteTime = csn; 142 } 143 144 if (csn.isNewerThan(lastUpdateTime)) 145 { 146 lastUpdateTime = csn; 147 } 148 } 149 150 /** 151 * Update the historical of this attribute after deleting a set of values. 152 * 153 * @param attr 154 * the attribute containing the set of values that were deleted 155 * @param csn 156 * time when the delete was done 157 */ 158 void delete(Attribute attr, CSN csn) 159 { 160 for (ByteString val : attr) 161 { 162 delete(val, csn); 163 } 164 } 165 166 /** 167 * Update the historical of this attribute after a delete value. 168 * 169 * @param val value that was deleted 170 * @param csn time when the delete was done 171 */ 172 void delete(ByteString val, CSN csn) 173 { 174 update(csn, new AttrValueHistorical(val, null, csn)); 175 } 176 177 /** 178 * Update the historical information when values are added. 179 * 180 * @param attr 181 * the attribute containing the set of added values 182 * @param csn 183 * time when the add is done 184 */ 185 private void add(Attribute attr, CSN csn) 186 { 187 for (ByteString val : attr) 188 { 189 add(val, csn); 190 } 191 } 192 193 /** 194 * Update the historical information when a value is added. 195 * 196 * @param addedValue 197 * values that was added 198 * @param csn 199 * time when the value was added 200 */ 201 void add(ByteString addedValue, CSN csn) 202 { 203 update(csn, new AttrValueHistorical(addedValue, csn, null)); 204 } 205 206 private void update(CSN csn, AttrValueHistorical valInfo) 207 { 208 updateValInfo(valInfo, valInfo); 209 if (csn.isNewerThan(lastUpdateTime)) 210 { 211 lastUpdateTime = csn; 212 } 213 } 214 215 private void updateValInfo(AttrValueHistorical oldValInfo, AttrValueHistorical newValInfo) 216 { 217 valuesHist.remove(oldValInfo); 218 valuesHist.put(newValInfo, newValInfo); 219 } 220 221 @Override 222 public Set<AttrValueHistorical> getValuesHistorical() 223 { 224 return valuesHist.keySet(); 225 } 226 227 @Override 228 public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn, 229 Entry modifiedEntry, Modification m) 230 { 231 if (csn.isNewerThanOrEqualTo(getLastUpdateTime()) 232 && m.getModificationType() == ModificationType.REPLACE) 233 { 234 processLocalOrNonConflictModification(csn, m); 235 return false;// the attribute was not modified more recently 236 } 237 // We are replaying an operation that was already done 238 // on another master server and this operation has a potential 239 // conflict with some more recent operations on this same entry 240 // we need to take the more complex path to solve them 241 return replayPotentialConflictModification(modsIterator, csn, modifiedEntry, m); 242 } 243 244 private boolean replayPotentialConflictModification(Iterator<Modification> modsIterator, CSN csn, 245 Entry modifiedEntry, Modification m) 246 { 247 // the attribute was modified after this change -> conflict 248 switch (m.getModificationType().asEnum()) 249 { 250 case DELETE: 251 if (csn.isOlderThan(getDeleteTime())) 252 { 253 /* this delete is already obsoleted by a more recent delete 254 * skip this mod 255 */ 256 modsIterator.remove(); 257 return true; 258 } 259 260 if (!processDeleteConflict(csn, m, modifiedEntry)) 261 { 262 modsIterator.remove(); 263 return true; 264 } 265 return false; 266 267 case ADD: 268 if (!processAddConflict(csn, m)) 269 { 270 modsIterator.remove(); 271 return true; 272 } 273 return false; 274 275 case REPLACE: 276 if (csn.isOlderThan(getDeleteTime())) 277 { 278 /* this replace is already obsoleted by a more recent delete 279 * skip this mod 280 */ 281 modsIterator.remove(); 282 return true; 283 } 284 285 /* save the values that are added by the replace operation into addedValues 286 * first process the replace as a delete operation 287 * -> this generates a list of values that should be kept 288 * then process the addedValues as if they were coming from an add 289 * -> this generates the list of values that needs to be added 290 * concatenate the 2 generated lists into a replace 291 */ 292 boolean conflict = false; 293 Attribute addedValues = m.getAttribute(); 294 m.setAttribute(new AttributeBuilder(addedValues, true).toAttribute()); 295 296 processDeleteConflict(csn, m, modifiedEntry); 297 Attribute keptValues = m.getAttribute(); 298 299 m.setAttribute(addedValues); 300 if (!processAddConflict(csn, m)) 301 { 302 modsIterator.remove(); 303 conflict = true; 304 } 305 306 AttributeBuilder builder = new AttributeBuilder(keptValues); 307 builder.addAll(m.getAttribute()); 308 m.setAttribute(builder.toAttribute()); 309 return conflict; 310 311 case INCREMENT: 312 // TODO : FILL ME 313 return false; 314 315 default: 316 return false; 317 } 318 } 319 320 @Override 321 public void processLocalOrNonConflictModification(CSN csn, Modification mod) 322 { 323 /* 324 * The operation is either a non-conflicting operation or a local operation 325 * so there is no need to check the historical information for conflicts. 326 * If this is a local operation, then this code is run after 327 * the pre-operation phase. 328 * If this is a non-conflicting replicated operation, this code is run 329 * during the handleConflictResolution(). 330 */ 331 332 Attribute modAttr = mod.getAttribute(); 333 AttributeType type = modAttr.getAttributeType(); 334 335 switch (mod.getModificationType().asEnum()) 336 { 337 case DELETE: 338 if (modAttr.isEmpty()) 339 { 340 delete(csn); 341 } 342 else 343 { 344 delete(modAttr, csn); 345 } 346 break; 347 348 case ADD: 349 if (type.isSingleValue()) 350 { 351 delete(csn); 352 } 353 add(modAttr, csn); 354 break; 355 356 case REPLACE: 357 /* TODO : can we replace specific attribute values ????? */ 358 delete(csn); 359 add(modAttr, csn); 360 break; 361 362 case INCREMENT: 363 /* FIXME : we should update CSN */ 364 break; 365 } 366 } 367 368 /** 369 * Process a delete attribute values that is conflicting with a previous modification. 370 * 371 * @param csn The CSN of the currently processed change 372 * @param m the modification that is being processed 373 * @param modifiedEntry the entry that is modified (before current mod) 374 * @return {@code true} if no conflict was detected, {@code false} otherwise. 375 */ 376 private boolean processDeleteConflict(CSN csn, Modification m, Entry modifiedEntry) 377 { 378 /* 379 * We are processing a conflicting DELETE modification 380 * 381 * This code is written on the assumption that conflict are 382 * rare. We therefore don't care much about the performance 383 * However since it is rarely executed this code needs to be 384 * as simple as possible to make sure that all paths are tested. 385 * In this case the most simple seem to change the DELETE 386 * in a REPLACE modification that keeps all values 387 * more recent that the DELETE. 388 * we are therefore going to change m into a REPLACE that will keep 389 * all the values that have been updated after the DELETE time 390 * If a value is present in the entry without any state information 391 * it must be removed so we simply ignore them 392 */ 393 394 Attribute modAttr = m.getAttribute(); 395 if (modAttr.isEmpty()) 396 { 397 // We are processing a DELETE attribute modification 398 m.setModificationType(ModificationType.REPLACE); 399 AttributeBuilder builder = new AttributeBuilder(modAttr, true); 400 401 for (Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator(); it.hasNext();) 402 { 403 AttrValueHistorical valInfo = it.next(); 404 405 if (csn.isOlderThan(valInfo.getValueUpdateTime())) 406 { 407 // this value has been updated after this delete, 408 // therefore this value must be kept 409 builder.add(valInfo.getAttributeValue()); 410 } 411 else if (csn.isNewerThanOrEqualTo(valInfo.getValueDeleteTime())) 412 { 413 /* 414 * this value is going to be deleted, remove it from historical 415 * information unless it is a Deleted attribute value that is 416 * more recent than this DELETE 417 */ 418 it.remove(); 419 } 420 } 421 422 m.setAttribute(builder.toAttribute()); 423 424 if (csn.isNewerThan(getDeleteTime())) 425 { 426 deleteTime = csn; 427 } 428 if (csn.isNewerThan(getLastUpdateTime())) 429 { 430 lastUpdateTime = csn; 431 } 432 } 433 else 434 { 435 // we are processing DELETE of some attribute values 436 AttributeBuilder builder = new AttributeBuilder(modAttr); 437 438 for (ByteString val : modAttr) 439 { 440 boolean deleteIt = true; // true if the delete must be done 441 boolean addedInCurrentOp = false; 442 443 // update historical information 444 AttrValueHistorical valInfo = new AttrValueHistorical(val, null, csn); 445 AttrValueHistorical oldValInfo = valuesHist.get(valInfo); 446 if (oldValInfo != null) 447 { 448 // this value already exist in the historical information 449 if (csn.equals(oldValInfo.getValueUpdateTime())) 450 { 451 // This value was added earlier in the same operation 452 // we need to keep the delete. 453 addedInCurrentOp = true; 454 } 455 if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()) && 456 csn.isNewerThanOrEqualTo(oldValInfo.getValueUpdateTime())) 457 { 458 updateValInfo(oldValInfo, valInfo); 459 } 460 else if (oldValInfo.isUpdate()) 461 { 462 deleteIt = false; 463 } 464 } 465 else 466 { 467 updateValInfo(oldValInfo, valInfo); 468 } 469 470 /* if the attribute value is not to be deleted 471 * or if attribute value is not present suppress it from the 472 * MOD to make sure the delete is going to succeed 473 */ 474 if (!deleteIt 475 || (!modifiedEntry.hasValue(modAttr.getAttributeType(), modAttr 476 .getOptions(), val) && ! addedInCurrentOp)) 477 { 478 // this value was already deleted before and therefore 479 // this should not be replayed. 480 builder.remove(val); 481 if (builder.isEmpty()) 482 { 483 // This was the last values in the set of values to be deleted. 484 // this MOD must therefore be skipped. 485 return false; 486 } 487 } 488 } 489 490 m.setAttribute(builder.toAttribute()); 491 492 if (csn.isNewerThan(getLastUpdateTime())) 493 { 494 lastUpdateTime = csn; 495 } 496 } 497 498 return true; 499 } 500 501 /** 502 * Process a add attribute values that is conflicting with a previous modification. 503 * 504 * @param csn 505 * the historical info associated to the entry 506 * @param m 507 * the modification that is being processed 508 * @return {@code true} if no conflict was detected, {@code false} otherwise. 509 */ 510 private boolean processAddConflict(CSN csn, Modification m) 511 { 512 /* 513 * if historicalattributedelete is newer forget this mod else find 514 * attr value if does not exist add historicalvalueadded timestamp 515 * add real value in entry else if timestamp older and already was 516 * historicalvalueadded update historicalvalueadded else if 517 * timestamp older and was historicalvaluedeleted change 518 * historicalvaluedeleted into historicalvalueadded add value in 519 * real entry 520 */ 521 522 if (csn.isOlderThan(getDeleteTime())) 523 { 524 /* A delete has been done more recently than this add 525 * forget this MOD ADD 526 */ 527 return false; 528 } 529 530 AttributeBuilder builder = new AttributeBuilder(m.getAttribute()); 531 for (ByteString addVal : m.getAttribute()) 532 { 533 AttrValueHistorical valInfo = new AttrValueHistorical(addVal, csn, null); 534 AttrValueHistorical oldValInfo = valuesHist.get(valInfo); 535 if (oldValInfo == null) 536 { 537 /* this value does not exist yet 538 * add it in the historical information 539 * let the operation process normally 540 */ 541 valuesHist.put(valInfo, valInfo); 542 } 543 else 544 { 545 if (oldValInfo.isUpdate()) 546 { 547 /* if the value is already present 548 * check if the updateTime must be updated 549 * in all cases suppress this value from the value list 550 * as it is already present in the entry 551 */ 552 if (csn.isNewerThan(oldValInfo.getValueUpdateTime())) 553 { 554 updateValInfo(oldValInfo, valInfo); 555 } 556 builder.remove(addVal); 557 } 558 else 559 { // it is a delete 560 /* this value is marked as a deleted value 561 * check if this mod is more recent the this delete 562 */ 563 if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime())) 564 { 565 updateValInfo(oldValInfo, valInfo); 566 } 567 else 568 { 569 /* the delete that is present in the historical information 570 * is more recent so it must win, 571 * remove this value from the list of values to add 572 * don't update the historical information 573 */ 574 builder.remove(addVal); 575 } 576 } 577 } 578 } 579 580 Attribute attr = builder.toAttribute(); 581 m.setAttribute(attr); 582 583 if (attr.isEmpty()) 584 { 585 return false; 586 } 587 588 if (csn.isNewerThan(getLastUpdateTime())) 589 { 590 lastUpdateTime = csn; 591 } 592 return true; 593 } 594 595 @Override 596 public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn) 597 { 598 switch (histKey) 599 { 600 case ADD: 601 if (value != null) 602 { 603 add(value, csn); 604 } 605 break; 606 607 case DEL: 608 if (value != null) 609 { 610 delete(value, csn); 611 } 612 break; 613 614 case REPL: 615 delete(csn); 616 if (value != null) 617 { 618 add(value, csn); 619 } 620 break; 621 622 case ATTRDEL: 623 delete(csn); 624 break; 625 } 626 } 627 628 @Override 629 public String toString() 630 { 631 final StringBuilder sb = new StringBuilder(); 632 sb.append(getClass().getSimpleName()).append("("); 633 boolean deleteAppended = false; 634 if (deleteTime != null) 635 { 636 deleteAppended = true; 637 sb.append("deleteTime=").append(deleteTime); 638 } 639 if (lastUpdateTime != null) 640 { 641 if (deleteAppended) 642 { 643 sb.append(", "); 644 } 645 sb.append("lastUpdateTime=").append(lastUpdateTime); 646 } 647 sb.append(", valuesHist=").append(valuesHist.keySet()); 648 sb.append(")"); 649 return sb.toString(); 650 } 651}