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 * http://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<Modification>(); 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, previous ); 225 reverseModifications.add( 0, reverseModification ); 226 break; 227 228 default: 229 break; // Do nothing 230 231 } 232 233 AttributeUtils.applyModification( clonedEntry, modification ); 234 235 } 236 237 // Special case if we don't have any reverse modifications 238 if ( reverseModifications.size() == 0 ) 239 { 240 throw new IllegalArgumentException( I18n.err( I18n.ERR_12073, forwardModifications ) ); 241 } 242 243 // Now, push the reversed list into the entry 244 for ( Modification modification : reverseModifications ) 245 { 246 entry.addModification( modification ); 247 } 248 249 // Return the reverted entry 250 return entry; 251 } 252 253 254 /** 255 * Compute a reverse LDIF for a forward change which if in LDIF format 256 * would represent a Move operation. Hence there is no newRdn in the 257 * picture here. 258 * 259 * @param newSuperiorDn the new parent dn to be (must not be null) 260 * @param modifiedDn the dn of the entry being moved (must not be null) 261 * @return a reverse LDIF 262 * @throws LdapException if something went wrong 263 */ 264 public static LdifEntry reverseMove( Dn newSuperiorDn, Dn modifiedDn ) throws LdapException 265 { 266 LdifEntry entry = new LdifEntry(); 267 Dn currentParent = null; 268 Rdn currentRdn = null; 269 Dn newDn = null; 270 271 if ( newSuperiorDn == null ) 272 { 273 throw new IllegalArgumentException( I18n.err( I18n.ERR_12074 ) ); 274 } 275 276 if ( modifiedDn == null ) 277 { 278 throw new IllegalArgumentException( I18n.err( I18n.ERR_12075 ) ); 279 } 280 281 if ( modifiedDn.size() == 0 ) 282 { 283 throw new IllegalArgumentException( I18n.err( I18n.ERR_12076 ) ); 284 } 285 286 currentParent = modifiedDn; 287 currentRdn = currentParent.getRdn(); 288 currentParent = currentParent.getParent(); 289 290 newDn = newSuperiorDn; 291 newDn = newDn.add( modifiedDn.getRdn() ); 292 293 entry.setChangeType( ChangeType.ModDn ); 294 entry.setDn( newDn ); 295 entry.setNewRdn( currentRdn.getName() ); 296 entry.setNewSuperior( currentParent.getName() ); 297 entry.setDeleteOldRdn( false ); 298 return entry; 299 } 300 301 302 /** 303 * A small helper class to compute the simple revert. 304 */ 305 private static LdifEntry revertEntry( Entry entry, Dn newDn, Dn newSuperior, Rdn oldRdn, Rdn newRdn ) 306 throws LdapInvalidDnException 307 { 308 LdifEntry reverted = new LdifEntry(); 309 310 // We have a composite old Rdn, something like A=a+B=b 311 // It does not matter if the RDNs overlap 312 reverted.setChangeType( ChangeType.ModRdn ); 313 314 if ( newSuperior != null ) 315 { 316 Dn restoredDn = newSuperior.add( newRdn ); 317 reverted.setDn( restoredDn ); 318 } 319 else 320 { 321 reverted.setDn( newDn ); 322 } 323 324 reverted.setNewRdn( oldRdn.getName() ); 325 326 // Is the newRdn's value present in the entry ? 327 // ( case 3, 4 and 5) 328 // If keepOldRdn = true, we cover case 4 and 5 329 boolean keepOldRdn = entry.contains( newRdn.getNormType(), newRdn.getNormValue() ); 330 331 reverted.setDeleteOldRdn( !keepOldRdn ); 332 333 if ( newSuperior != null ) 334 { 335 Dn oldSuperior = entry.getDn(); 336 337 oldSuperior = oldSuperior.getParent(); 338 reverted.setNewSuperior( oldSuperior.getName() ); 339 } 340 341 return reverted; 342 } 343 344 345 /** 346 * A helper method to generate the modified attribute after a rename. 347 */ 348 private static LdifEntry generateModify( Dn parentDn, Entry entry, Rdn oldRdn, Rdn newRdn ) 349 { 350 LdifEntry restored = new LdifEntry(); 351 restored.setChangeType( ChangeType.Modify ); 352 353 // We have to use the parent Dn, the entry has already 354 // been renamed 355 restored.setDn( parentDn ); 356 357 for ( Ava ava : newRdn ) 358 { 359 // No need to add something which has already been added 360 // in the previous modification 361 if ( !entry.contains( ava.getNormType(), ava.getNormValue().getString() ) 362 && !( ava.getNormType().equals( oldRdn.getNormType() ) && ava.getNormValue().getString().equals( 363 oldRdn.getNormValue().getString() ) ) ) 364 { 365 // Create the modification, which is an Remove 366 Modification modification = new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, 367 new DefaultAttribute( ava.getType(), ava.getValue().getString() ) ); 368 369 restored.addModification( modification ); 370 } 371 } 372 373 return restored; 374 } 375 376 377 /** 378 * A helper method which generates a reverted entry 379 */ 380 private static LdifEntry generateReverted( Dn newSuperior, Rdn newRdn, Dn newDn, Rdn oldRdn, boolean deleteOldRdn ) 381 throws LdapInvalidDnException 382 { 383 LdifEntry reverted = new LdifEntry(); 384 reverted.setChangeType( ChangeType.ModRdn ); 385 386 if ( newSuperior != null ) 387 { 388 Dn restoredDn = newSuperior.add( newRdn ); 389 reverted.setDn( restoredDn ); 390 } 391 else 392 { 393 reverted.setDn( newDn ); 394 } 395 396 reverted.setNewRdn( oldRdn.getName() ); 397 398 if ( newSuperior != null ) 399 { 400 Dn oldSuperior = newDn; 401 402 oldSuperior = oldSuperior.getParent(); 403 reverted.setNewSuperior( oldSuperior.getName() ); 404 } 405 406 // Delete the newRDN values 407 reverted.setDeleteOldRdn( deleteOldRdn ); 408 409 return reverted; 410 } 411 412 413 /** 414 * Revert a Dn to it's previous version by removing the first Rdn and adding the given Rdn. 415 * It's a rename operation. The biggest issue is that we have many corner cases, depending 416 * on the RDNs we are manipulating, and on the content of the initial entry. 417 * 418 * @param entry The initial Entry 419 * @param newRdn The new Rdn 420 * @param deleteOldRdn A flag which tells to delete the old Rdn AVAs 421 * @return A list of LDIF reverted entries 422 * @throws LdapInvalidDnException If the name reverting failed 423 */ 424 public static List<LdifEntry> reverseRename( Entry entry, Rdn newRdn, boolean deleteOldRdn ) 425 throws LdapInvalidDnException 426 { 427 return reverseMoveAndRename( entry, null, newRdn, deleteOldRdn ); 428 } 429 430 431 /** 432 * Revert a Dn to it's previous version by removing the first Rdn and adding the given Rdn. 433 * It's a rename operation. The biggest issue is that we have many corner cases, depending 434 * on the RDNs we are manipulating, and on the content of the initial entry. 435 * 436 * @param entry The initial Entry 437 * @param newSuperior The new superior Dn (can be null if it's just a rename) 438 * @param newRdn The new Rdn 439 * @param deleteOldRdn A flag which tells to delete the old Rdn AVAs 440 * @return A list of LDIF reverted entries 441 * @throws LdapInvalidDnException If the name reverting failed 442 */ 443 public static List<LdifEntry> reverseMoveAndRename( Entry entry, Dn newSuperior, Rdn newRdn, boolean deleteOldRdn ) 444 throws LdapInvalidDnException 445 { 446 Dn parentDn = entry.getDn(); 447 Dn newDn = null; 448 449 if ( newRdn == null ) 450 { 451 throw new IllegalArgumentException( I18n.err( I18n.ERR_12077 ) ); 452 } 453 454 if ( parentDn == null ) 455 { 456 throw new IllegalArgumentException( I18n.err( I18n.ERR_12078 ) ); 457 } 458 459 if ( parentDn.size() == 0 ) 460 { 461 throw new IllegalArgumentException( I18n.err( I18n.ERR_12079 ) ); 462 } 463 464 parentDn = entry.getDn(); 465 Rdn oldRdn = parentDn.getRdn(); 466 467 newDn = parentDn; 468 newDn = newDn.getParent(); 469 newDn = newDn.add( newRdn ); 470 471 List<LdifEntry> entries = new ArrayList<LdifEntry>( 1 ); 472 LdifEntry reverted = new LdifEntry(); 473 474 // Start with the cases here 475 if ( newRdn.size() == 1 ) 476 { 477 // We have a simple new Rdn, something like A=a 478 reverted = revertEntry( entry, newDn, newSuperior, oldRdn, newRdn ); 479 480 entries.add( reverted ); 481 } 482 else 483 { 484 // We have a composite new Rdn, something like A=a+B=b 485 if ( oldRdn.size() == 1 ) 486 { 487 // The old Rdn is simple 488 boolean overlapping = false; 489 boolean existInEntry = false; 490 491 // Does it overlap ? 492 // Is the new Rdn AVAs contained into the entry? 493 for ( Ava atav : newRdn ) 494 { 495 if ( atav.equals( oldRdn.getAva() ) ) 496 { 497 // They overlap 498 overlapping = true; 499 } 500 else 501 { 502 if ( entry.contains( atav.getNormType(), atav.getNormValue().getString() ) ) 503 { 504 existInEntry = true; 505 } 506 } 507 } 508 509 if ( overlapping ) 510 { 511 // The new Rdn includes the old one 512 if ( existInEntry ) 513 { 514 // Some of the new Rdn AVAs existed in the entry 515 // We have to restore them, but we also have to remove 516 // the new values 517 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 518 519 entries.add( reverted ); 520 521 // Now, restore the initial values 522 LdifEntry restored = generateModify( parentDn, entry, oldRdn, newRdn ); 523 524 entries.add( restored ); 525 } 526 else 527 { 528 // This is the simplest case, we don't have to restore 529 // some existing values (case 8.1 and 9.1) 530 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 531 532 entries.add( reverted ); 533 } 534 } 535 else 536 { 537 if ( existInEntry ) 538 { 539 // Some of the new Rdn AVAs existed in the entry 540 // We have to restore them, but we also have to remove 541 // the new values 542 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 543 544 entries.add( reverted ); 545 546 LdifEntry restored = generateModify( parentDn, entry, oldRdn, newRdn ); 547 548 entries.add( restored ); 549 } 550 else 551 { 552 // A much simpler case, as we just have to remove the newRDN 553 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 554 555 entries.add( reverted ); 556 } 557 } 558 } 559 else 560 { 561 // We have a composite new Rdn, something like A=a+B=b 562 // Does the Rdn overlap ? 563 boolean overlapping = false; 564 boolean existInEntry = false; 565 566 Set<Ava> oldAtavs = new HashSet<Ava>(); 567 568 // We first build a set with all the oldRDN ATAVs 569 for ( Ava atav : oldRdn ) 570 { 571 oldAtavs.add( atav ); 572 } 573 574 // Now we loop on the newRDN ATAVs to evaluate if the Rdns are overlaping 575 // and if the newRdn ATAVs are present in the entry 576 for ( Ava atav : newRdn ) 577 { 578 if ( oldAtavs.contains( atav ) ) 579 { 580 overlapping = true; 581 } 582 else if ( entry.contains( atav.getNormType(), atav.getNormValue().getString() ) ) 583 { 584 existInEntry = true; 585 } 586 } 587 588 if ( overlapping ) 589 { 590 // They overlap 591 if ( existInEntry ) 592 { 593 // In this case, we have to reestablish the removed ATAVs 594 // (Cases 12.2 and 13.2) 595 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 596 597 entries.add( reverted ); 598 } 599 else 600 { 601 // We can simply remove all the new Rdn atavs, as the 602 // overlapping values will be re-created. 603 // (Cases 12.1 and 13.1) 604 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 605 606 entries.add( reverted ); 607 } 608 } 609 else 610 { 611 // No overlapping 612 if ( existInEntry ) 613 { 614 // In this case, we have to reestablish the removed ATAVs 615 // (Cases 10.2 and 11.2) 616 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, KEEP_OLD_RDN ); 617 618 entries.add( reverted ); 619 620 LdifEntry restored = generateModify( parentDn, entry, oldRdn, newRdn ); 621 622 entries.add( restored ); 623 } 624 else 625 { 626 // We are safe ! We can delete all the new Rdn ATAVs 627 // (Cases 10.1 and 11.1) 628 reverted = generateReverted( newSuperior, newRdn, newDn, oldRdn, DELETE_OLD_RDN ); 629 630 entries.add( reverted ); 631 } 632 } 633 } 634 } 635 636 return entries; 637 } 638}