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.ldap.entry.Entry;
032    import org.apache.directory.shared.ldap.entry.EntryAttribute;
033    import org.apache.directory.shared.ldap.entry.Modification;
034    import org.apache.directory.shared.ldap.entry.ModificationOperation;
035    import org.apache.directory.shared.ldap.entry.client.ClientModification;
036    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
037    import org.apache.directory.shared.ldap.name.AttributeTypeAndValue;
038    import org.apache.directory.shared.ldap.name.LdapDN;
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( LdapDN 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( LdapDN dn, Entry deletedEntry ) throws NamingException
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( LdapDN dn, List<Modification> forwardModifications, Entry modifiedEntry )
114            throws NamingException
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( "Could not deduce reverse modifications from provided modifications: "
227                    + 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( LdapDN newSuperiorDn, LdapDN modifiedDn ) throws NamingException
252        {
253            LdifEntry entry = new LdifEntry();
254            LdapDN currentParent = null;
255            Rdn currentRdn = null;
256            LdapDN newDn = null;
257    
258            if ( newSuperiorDn == null )
259            {
260                throw new NullPointerException( "newSuperiorDn must not be null" );
261            }
262    
263            if ( modifiedDn == null )
264            {
265                throw new NullPointerException( "modifiedDn must not be null" );
266            }
267    
268            if ( modifiedDn.size() == 0 )
269            {
270                throw new IllegalArgumentException( "Don't think about moving the rootDSE." );
271            }
272    
273            currentParent = ( LdapDN ) modifiedDn.clone();
274            currentRdn = currentParent.getRdn();
275            currentParent.remove( currentParent.size() - 1 );
276    
277            newDn = ( LdapDN ) 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.getUpName() );
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, LdapDN newDn, 
293            LdapDN 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                LdapDN restoredDn = (LdapDN)((LdapDN)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                LdapDN oldSuperior = ( LdapDN ) entry.getDn().clone();
323    
324                oldSuperior.remove( oldSuperior.size() - 1 );
325                reverted.setNewSuperior( oldSuperior.getUpName() );
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( LdapDN 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 ( AttributeTypeAndValue 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( LdapDN newSuperior, Rdn newRdn, LdapDN 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                LdapDN restoredDn = (LdapDN)((LdapDN)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                LdapDN oldSuperior = ( LdapDN ) newDn.clone();
389    
390                oldSuperior.remove( oldSuperior.size() - 1 );
391                reverted.setNewSuperior( oldSuperior.getUpName() );
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, LdapDN newSuperior, Rdn newRdn, boolean deleteOldRdn ) throws NamingException
431        {
432            LdapDN parentDn = entry.getDn();
433            LdapDN newDn = null;
434    
435            if ( newRdn == null )
436            {
437                throw new NullPointerException( "The newRdn must not be null" );
438            }
439    
440            if ( parentDn == null )
441            {
442                throw new NullPointerException( "The modified Dn must not be null" );
443            }
444    
445            if ( parentDn.size() == 0 )
446            {
447                throw new IllegalArgumentException( "Don't think about renaming the rootDSE." );
448            }
449    
450            parentDn = ( LdapDN ) entry.getDn().clone();
451            Rdn oldRdn = parentDn.getRdn();
452    
453            newDn = ( LdapDN ) 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( "Can't rename an entry using the same name ..." ); 
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 ( AttributeTypeAndValue 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<AttributeTypeAndValue> oldAtavs = new HashSet<AttributeTypeAndValue>();
562    
563                    // We first build a set with all the oldRDN ATAVs 
564                    for ( AttributeTypeAndValue 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 ( AttributeTypeAndValue 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    }