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