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 }