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