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