001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.model.ldif; 021 022 023import java.util.ArrayList; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Set; 027 028import org.apache.directory.api.i18n.I18n; 029import org.apache.directory.api.ldap.model.entry.Attribute; 030import org.apache.directory.api.ldap.model.entry.AttributeUtils; 031import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 032import org.apache.directory.api.ldap.model.entry.DefaultModification; 033import org.apache.directory.api.ldap.model.entry.Entry; 034import org.apache.directory.api.ldap.model.entry.Modification; 035import org.apache.directory.api.ldap.model.entry.ModificationOperation; 036import org.apache.directory.api.ldap.model.exception.LdapException; 037import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; 038import org.apache.directory.api.ldap.model.name.Ava; 039import org.apache.directory.api.ldap.model.name.Dn; 040import org.apache.directory.api.ldap.model.name.Rdn; 041 042 043/** 044 * A helper class which provides methods to reverse a LDIF modification operation. 045 * 046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 047 */ 048public final class LdifRevertor 049{ 050 /** Flag used when we want to delete the old Rdn */ 051 public static final boolean DELETE_OLD_RDN = true; 052 053 /** Flag used when we want to keep the old Rdn */ 054 public static final boolean KEEP_OLD_RDN = false; 055 056 057 /** 058 * Private constructor. 059 */ 060 private LdifRevertor() 061 { 062 } 063 064 065 /** 066 * Compute a reverse LDIF of an AddRequest. It's simply a delete request 067 * of the added entry 068 * 069 * @param dn the dn of the added entry 070 * @return a reverse LDIF 071 */ 072 public static LdifEntry reverseAdd( Dn dn ) 073 { 074 LdifEntry entry = new LdifEntry(); 075 entry.setChangeType( ChangeType.Delete ); 076 entry.setDn( dn ); 077 return entry; 078 } 079 080 081 /** 082 * Compute a reverse LDIF of a DeleteRequest. We have to get the previous 083 * entry in order to restore it. 084 * 085 * @param dn The deleted entry Dn 086 * @param deletedEntry The entry which has been deleted 087 * @return A reverse LDIF 088 * @throws LdapException If something went wrong 089 */ 090 public static LdifEntry reverseDel( Dn dn, Entry deletedEntry ) throws LdapException 091 { 092 LdifEntry entry = new LdifEntry(); 093 094 entry.setDn( dn ); 095 entry.setChangeType( ChangeType.Add ); 096 097 for ( Attribute attribute : deletedEntry ) 098 { 099 entry.addAttribute( attribute ); 100 } 101 102 return entry; 103 } 104 105 106 /** 107 * 108 * Compute the reversed LDIF for a modify request. We will deal with the 109 * three kind of modifications : 110 * <ul> 111 * <li>add</li> 112 * <li>remove</li> 113 * <li>replace</li> 114 * </ul> 115 * 116 * As the modifications should be issued in a reversed order ( ie, for 117 * the initials modifications {A, B, C}, the reversed modifications will 118 * be ordered like {C, B, A}), we will change the modifications order. 119 * 120 * @param dn the dn of the modified entry 121 * @param forwardModifications the modification items for the forward change 122 * @param modifiedEntry The modified entry. Necessary for the destructive modifications 123 * @return A reversed LDIF 124 * @throws LdapException If something went wrong 125 */ 126 public static LdifEntry reverseModify( Dn dn, List<Modification> forwardModifications, Entry modifiedEntry ) 127 throws LdapException 128 { 129 // First, protect the original entry by cloning it : we will modify it 130 Entry clonedEntry = modifiedEntry.clone(); 131 132 LdifEntry entry = new LdifEntry(); 133 entry.setChangeType( ChangeType.Modify ); 134 135 entry.setDn( dn ); 136 137 // As the reversed modifications should be pushed in reversed order, 138 // we create a list to temporarily store the modifications. 139 List<Modification> reverseModifications = new ArrayList<>(); 140 141 // Loop through all the modifications. For each modification, we will 142 // have to apply it to the modified entry in order to be able to generate 143 // the reversed modification 144 for ( Modification modification : forwardModifications ) 145 { 146 switch ( modification.getOperation() ) 147 { 148 case ADD_ATTRIBUTE: 149 Attribute mod = modification.getAttribute(); 150 151 Attribute previous = clonedEntry.get( mod.getId() ); 152 153 if ( mod.equals( previous ) ) 154 { 155 continue; 156 } 157 158 Modification reverseModification = new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, 159 mod ); 160 reverseModifications.add( 0, reverseModification ); 161 break; 162 163 case REMOVE_ATTRIBUTE: 164 mod = modification.getAttribute(); 165 166 previous = clonedEntry.get( mod.getId() ); 167 168 if ( previous == null ) 169 { 170 // Nothing to do if the previous attribute didn't exist 171 continue; 172 } 173 174 if ( mod.get() == null ) 175 { 176 reverseModification = new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, previous ); 177 reverseModifications.add( 0, reverseModification ); 178 break; 179 } 180 181 reverseModification = new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, mod ); 182 reverseModifications.add( 0, reverseModification ); 183 break; 184 185 case REPLACE_ATTRIBUTE: 186 mod = modification.getAttribute(); 187 188 previous = clonedEntry.get( mod.getId() ); 189 190 /* 191 * The server accepts without complaint replace 192 * modifications to non-existing attributes in the 193 * entry. When this occurs nothing really happens 194 * but this method freaks out. To prevent that we 195 * make such no-op modifications produce the same 196 * modification for the reverse direction which should 197 * do nothing as well. 198 */ 199 if ( ( mod.get() == null ) && ( previous == null ) ) 200 { 201 reverseModification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 202 new DefaultAttribute( mod.getId() ) ); 203 reverseModifications.add( 0, reverseModification ); 204 continue; 205 } 206 207 if ( mod.get() == null ) 208 { 209 reverseModification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 210 previous ); 211 reverseModifications.add( 0, reverseModification ); 212 continue; 213 } 214 215 if ( previous == null ) 216 { 217 Attribute emptyAttribute = new DefaultAttribute( mod.getId() ); 218 reverseModification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 219 emptyAttribute ); 220 reverseModifications.add( 0, reverseModification ); 221 continue; 222 } 223 224 reverseModification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 225 previous ); 226 reverseModifications.add( 0, reverseModification ); 227 break; 228 229 case INCREMENT_ATTRIBUTE: 230 mod = modification.getAttribute(); 231 previous = clonedEntry.get( mod.getId() ); 232 233 reverseModification = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 234 previous ); 235 reverseModifications.add( 0, reverseModification ); 236 break; 237 238 default: 239 // Do nothing 240 break; 241 242 } 243 244 AttributeUtils.applyModification( clonedEntry, modification ); 245 246 } 247 248 // Special case if we don't have any reverse modifications 249 if ( reverseModifications.isEmpty() ) 250 { 251 throw new IllegalArgumentException( I18n.err( I18n.ERR_13465_CANT_DEDUCE_REVERSE_FOR_MOD, forwardModifications ) ); 252 } 253 254 // Now, push the reversed list into the entry 255 for ( Modification modification : reverseModifications ) 256 { 257 entry.addModification( modification ); 258 } 259 260 // Return the reverted entry 261 return entry; 262 } 263 264 265 /** 266 * Compute a reverse LDIF for a forward change which if in LDIF format 267 * would represent a Move operation. Hence there is no newRdn in the 268 * picture here. 269 * 270 * @param newSuperiorDn the new parent dn to be (must not be null) 271 * @param modifiedDn the dn of the entry being moved (must not be null) 272 * @return a reverse LDIF 273 * @throws LdapException if something went wrong 274 */ 275 public static LdifEntry reverseMove( Dn newSuperiorDn, Dn modifiedDn ) throws LdapException 276 { 277 LdifEntry entry = new LdifEntry(); 278 Dn currentParent; 279 Rdn currentRdn; 280 Dn newDn; 281 282 if ( newSuperiorDn == null ) 283 { 284 throw new IllegalArgumentException( I18n.err( I18n.ERR_13466_NEW_SUPERIOR_DN_NULL ) ); 285 } 286 287 if ( modifiedDn == null ) 288 { 289 throw new IllegalArgumentException( I18n.err( I18n.ERR_13467_NULL_MODIFIED_DN ) ); 290 } 291 292 if ( modifiedDn.size() == 0 ) 293 { 294 throw new IllegalArgumentException( I18n.err( I18n.ERR_13468_DONT_MOVE_ROOTDSE ) ); 295 } 296 297 currentParent = modifiedDn; 298 currentRdn = currentParent.getRdn(); 299 currentParent = currentParent.getParent(); 300 301 newDn = newSuperiorDn; 302 newDn = newDn.add( modifiedDn.getRdn() ); 303 304 entry.setChangeType( ChangeType.ModDn ); 305 entry.setDn( newDn ); 306 entry.setNewRdn( currentRdn.getName() ); 307 entry.setNewSuperior( currentParent.getName() ); 308 entry.setDeleteOldRdn( false ); 309 return entry; 310 } 311 312 313 /** 314 * A small helper class to compute the simple revert. 315 * 316 * @param entry The entry to revert 317 * @param newDn The new Dn 318 * @param newSuperior The new superior, if it has changed (null otherwise) 319 * @param oldRdn The old Rdn 320 * @param newRdn The new RDN if the RDN has changed 321 * @return The reverted entry 322 * @throws LdapInvalidDnException If the Dn is invalid 323 */ 324 private static LdifEntry revertEntry( Entry entry, Dn newDn, Dn newSuperior, Rdn oldRdn, Rdn newRdn ) 325 throws LdapInvalidDnException 326 { 327 LdifEntry reverted = new LdifEntry(); 328 329 // We have a composite old Rdn, something like A=a+B=b 330 // It does not matter if the RDNs overlap 331 reverted.setChangeType( ChangeType.ModRdn ); 332 333 if ( newSuperior != null ) 334 { 335 Dn restoredDn = newSuperior.add( newRdn ); 336 reverted.setDn( restoredDn ); 337 } 338 else 339 { 340 reverted.setDn( newDn ); 341 } 342 343 reverted.setNewRdn( oldRdn.getName() ); 344 345 // Is the newRdn's value present in the entry ? 346 // ( case 3, 4 and 5) 347 // If keepOldRdn = true, we cover case 4 and 5 348 boolean keepOldRdn = entry.contains( newRdn.getNormType(), newRdn.getValue() ); 349 350 reverted.setDeleteOldRdn( !keepOldRdn ); 351 352 if ( newSuperior != null ) 353 { 354 Dn oldSuperior = entry.getDn(); 355 356 oldSuperior = oldSuperior.getParent(); 357 reverted.setNewSuperior( oldSuperior.getName() ); 358 } 359 360 return reverted; 361 } 362 363 364 /** 365 * A helper method to generate the modified attribute after a rename. 366 * 367 * @param parentDn The parent Dn 368 * @param entry The entry to revert 369 * @param oldRdn The old Rdn 370 * @param newRdn The new Rdn 371 * @return The modified entry 372 */ 373 private static LdifEntry generateModify( Dn parentDn, Entry entry, Rdn oldRdn, Rdn newRdn ) 374 { 375 LdifEntry restored = new LdifEntry(); 376 restored.setChangeType( ChangeType.Modify ); 377 378 // We have to use the parent Dn, the entry has already 379 // been renamed 380 restored.setDn( parentDn ); 381 382 for ( Ava ava : newRdn ) 383 { 384 // No need to add something which has already been added 385 // in the previous modification 386 if ( !entry.contains( ava.getNormType(), ava.getValue() ) 387 && !( ava.getNormType().equals( oldRdn.getNormType() ) && ava.getValue().equals( 388 oldRdn.getValue() ) ) ) 389 { 390 // Create the modification, which is an Remove 391 Modification modification = new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, 392 new DefaultAttribute( ava.getType(), ava.getValue() ) ); 393 394 restored.addModification( modification ); 395 } 396 } 397 398 return restored; 399 } 400 401 402 /** 403 * A helper method which generates a reverted entry for a MODDN operation 404 * 405 * @param newSuperior The new superior, if it has changed (null otherwise) 406 * @param newRdn The new RDN if the RDN has changed 407 * @param newDn The new Dn 408 * @param oldRdn The old Rdn 409 * @param deleteOldRdn If the old Rdn attributes must be deleted or not 410 * @return The reverted entry 411 * @throws LdapInvalidDnException If the DN is invalid 412 */ 413 private static LdifEntry generateReverted( Dn newSuperior, Rdn newRdn, Dn newDn, Rdn oldRdn, boolean deleteOldRdn ) 414 throws LdapInvalidDnException 415 { 416 LdifEntry reverted = new LdifEntry(); 417 reverted.setChangeType( ChangeType.ModRdn ); 418 419 if ( newSuperior != null ) 420 { 421 Dn restoredDn = newSuperior.add( newRdn ); 422 reverted.setDn( restoredDn ); 423 } 424 else 425 { 426 reverted.setDn( newDn ); 427 } 428 429 reverted.setNewRdn( oldRdn.getName() ); 430 431 if ( newSuperior != null ) 432 { 433 Dn oldSuperior = newDn; 434 435 oldSuperior = oldSuperior.getParent(); 436 reverted.setNewSuperior( oldSuperior.getName() ); 437 } 438 439 // Delete the newRDN values 440 reverted.setDeleteOldRdn( deleteOldRdn ); 441 442 return reverted; 443 } 444 445 446 /** 447 * Revert a Dn to it's previous version by removing the first Rdn and adding the given Rdn. 448 * It's a rename operation. The biggest issue is that we have many corner cases, depending 449 * on the RDNs we are manipulating, and on the content of the initial entry. 450 * 451 * @param entry The initial Entry 452 * @param newRdn The new Rdn 453 * @param deleteOldRdn A flag which tells to delete the old Rdn AVAs 454 * @return A list of LDIF reverted entries 455 * @throws LdapInvalidDnException If the name reverting failed 456 */ 457 public static List<LdifEntry> reverseRename( Entry entry, Rdn newRdn, boolean deleteOldRdn ) 458 throws LdapInvalidDnException 459 { 460 return reverseMoveAndRename( entry, null, newRdn, deleteOldRdn ); 461 } 462 463 464 /** 465 * Revert a Dn to it's previous version by removing the first Rdn and adding the given Rdn. 466 * It's a rename operation. The biggest issue is that we have many corner cases, depending 467 * on the RDNs we are manipulating, and on the content of the initial entry. 468 * 469 * @param entry The initial Entry 470 * @param newSuperior The new superior Dn (can be null if it's just a rename) 471 * @param newRdn The new Rdn 472 * @param deleteOldRdn A flag which tells to delete the old Rdn AVAs 473 * @return A list of LDIF reverted entries 474 * @throws LdapInvalidDnException If the name reverting failed 475 */ 476 public static List<LdifEntry> reverseMoveAndRename( Entry entry, Dn newSuperior, Rdn newRdn, boolean deleteOldRdn ) 477 throws LdapInvalidDnException 478 { 479 Dn parentDn = entry.getDn(); 480 Dn newDn; 481 482 if ( newRdn == null ) 483 { 484 throw new IllegalArgumentException( I18n.err( I18n.ERR_13469_NULL_READ_DN ) ); 485 } 486 487 if ( parentDn == null ) 488 { 489 throw new IllegalArgumentException( I18n.err( I18n.ERR_13467_NULL_MODIFIED_DN ) ); 490 } 491 492 if ( parentDn.size() == 0 ) 493 { 494 throw new IllegalArgumentException( I18n.err( I18n.ERR_13470_DONT_RENAME_ROOTDSE ) ); 495 } 496 497 parentDn = entry.getDn(); 498 Rdn oldRdn = parentDn.getRdn(); 499 500 newDn = parentDn; 501 newDn = newDn.getParent(); 502 newDn = newDn.add( newRdn ); 503 504 List<LdifEntry> entries = new ArrayList<>( 1 ); 505 LdifEntry reverted; 506 507 // Start with the cases here 508 if ( newRdn.size() == 1 ) 509 { 510 // We have a simple new Rdn, something like A=a 511 reverted = revertEntry( entry, newDn, newSuperior, oldRdn, newRdn ); 512 513 entries.add( reverted ); 514 } 515 else 516 { 517 // We have a composite new Rdn, something like A=a+B=b 518 if ( oldRdn.size() == 1 ) 519 { 520 // The old Rdn is simple 521 boolean existInEntry = false; 522 523 // Does it overlap ? 524 // Is the new Rdn AVAs contained into the entry? 525 for ( Ava atav : newRdn ) 526 { 527 if ( !atav.equals( oldRdn.getAva() ) 528 && ( entry.contains( atav.getNormType(), atav.getValue() ) ) ) 529 { 530 existInEntry = true; 531 } 532 } 533 534 // The new Rdn includes the old one 535 if ( existInEntry ) 536 { 537 // Some of the new Rdn AVAs existed in the entry 538 // We have to restore them, but we also have to remove 539 // the new values 540 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 541 542 entries.add( reverted ); 543 544 // Now, restore the initial values 545 LdifEntry restored = generateModify( parentDn, entry, oldRdn, newRdn ); 546 547 entries.add( restored ); 548 } 549 else 550 { 551 // This is the simplest case, we don't have to restore 552 // some existing values (case 8.1 and 9.1) 553 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 554 555 entries.add( reverted ); 556 } 557 } 558 else 559 { 560 // We have a composite new Rdn, something like A=a+B=b 561 // Does the Rdn overlap ? 562 boolean overlapping = false; 563 boolean existInEntry = false; 564 565 Set<Ava> oldAtavs = new HashSet<>(); 566 567 // We first build a set with all the oldRDN ATAVs 568 for ( Ava atav : oldRdn ) 569 { 570 oldAtavs.add( atav ); 571 } 572 573 // Now we loop on the newRDN ATAVs to evaluate if the Rdns are overlaping 574 // and if the newRdn ATAVs are present in the entry 575 for ( Ava atav : newRdn ) 576 { 577 if ( oldAtavs.contains( atav ) ) 578 { 579 overlapping = true; 580 } 581 else if ( entry.contains( atav.getNormType(), atav.getValue() ) ) 582 { 583 existInEntry = true; 584 } 585 } 586 587 if ( overlapping ) 588 { 589 // They overlap 590 if ( existInEntry ) 591 { 592 // In this case, we have to reestablish the removed ATAVs 593 // (Cases 12.2 and 13.2) 594 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 595 596 entries.add( reverted ); 597 } 598 else 599 { 600 // We can simply remove all the new Rdn atavs, as the 601 // overlapping values will be re-created. 602 // (Cases 12.1 and 13.1) 603 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 604 605 entries.add( reverted ); 606 } 607 } 608 else 609 { 610 // No overlapping 611 if ( existInEntry ) 612 { 613 // In this case, we have to reestablish the removed ATAVs 614 // (Cases 10.2 and 11.2) 615 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 616 617 entries.add( reverted ); 618 619 LdifEntry restored = generateModify( parentDn, entry, oldRdn, newRdn ); 620 621 entries.add( restored ); 622 } 623 else 624 { 625 // We are safe ! We can delete all the new Rdn ATAVs 626 // (Cases 10.1 and 11.1) 627 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 628 629 entries.add( reverted ); 630 } 631 } 632 } 633 } 634 635 return entries; 636 } 637}