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