001    /*
002     * Copyright 2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2016 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.transformations;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Arrays;
027    import java.util.Collection;
028    import java.util.List;
029    
030    import com.unboundid.ldap.sdk.Attribute;
031    import com.unboundid.ldap.sdk.DN;
032    import com.unboundid.ldap.sdk.Entry;
033    import com.unboundid.ldap.sdk.Modification;
034    import com.unboundid.ldap.sdk.RDN;
035    import com.unboundid.ldif.LDIFAddChangeRecord;
036    import com.unboundid.ldif.LDIFChangeRecord;
037    import com.unboundid.ldif.LDIFDeleteChangeRecord;
038    import com.unboundid.ldif.LDIFModifyChangeRecord;
039    import com.unboundid.ldif.LDIFModifyDNChangeRecord;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    
043    
044    
045    /**
046     * This class provides an implementation of an entry and LDIF change record
047     * transformation that will alter DNs at or below a specified base DN to replace
048     * that base DN with a different base DN.  This replacement will be applied to
049     * the DNs of entries that are transformed, as well as in any attribute values
050     * that represent DNs at or below the specified base DN.
051     */
052    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
053    public final class MoveSubtreeTransformation
054           implements EntryTransformation, LDIFChangeRecordTransformation
055    {
056      // The source base DN to be replaced.
057      private final DN sourceDN;
058    
059      // A list of the RDNs in the target base DN.
060      private final List<RDN> targetRDNs;
061    
062    
063    
064      /**
065       * Creates a new move subtree transformation with the provided information.
066       *
067       * @param  sourceDN  The source base DN to be replaced with the target base
068       *                   DN.  It must not be {@code null}.
069       * @param  targetDN  The target base DN to use to replace the source base DN.
070       *                   It must not be {@code null}.
071       */
072      public MoveSubtreeTransformation(final DN sourceDN, final DN targetDN)
073      {
074        this.sourceDN = sourceDN;
075    
076        targetRDNs = Arrays.asList(targetDN.getRDNs());
077      }
078    
079    
080    
081      /**
082       * {@inheritDoc}
083       */
084      public Entry transformEntry(final Entry e)
085      {
086        if (e == null)
087        {
088          return null;
089        }
090    
091    
092        // Iterate through the attributes in the entry and make any appropriate DN
093        // replacements
094        final Collection<Attribute> originalAttributes = e.getAttributes();
095        final ArrayList<Attribute> newAttributes =
096             new ArrayList<Attribute>(originalAttributes.size());
097        for (final Attribute a : originalAttributes)
098        {
099          final String[] originalValues = a.getValues();
100          final String[] newValues = new String[originalValues.length];
101          for (int i=0; i < originalValues.length; i++)
102          {
103            newValues[i] = processString(originalValues[i]);
104          }
105    
106          newAttributes.add(new Attribute(a.getName(), newValues));
107        }
108    
109        return new Entry(processString(e.getDN()), newAttributes);
110      }
111    
112    
113    
114      /**
115       * {@inheritDoc}
116       */
117      public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
118      {
119        if (r == null)
120        {
121          return null;
122        }
123    
124    
125        if (r instanceof LDIFAddChangeRecord)
126        {
127          // Just use the same processing as for an entry.
128          final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
129          return new LDIFAddChangeRecord(transformEntry(addRecord.getEntryToAdd()),
130               addRecord.getControls());
131        }
132        if (r instanceof LDIFDeleteChangeRecord)
133        {
134          return new LDIFDeleteChangeRecord(processString(r.getDN()),
135               r.getControls());
136        }
137        else if (r instanceof LDIFModifyChangeRecord)
138        {
139          final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r;
140          final Modification[] originalMods = modRecord.getModifications();
141          final Modification[] newMods = new Modification[originalMods.length];
142          for (int i=0; i < originalMods.length; i++)
143          {
144            final Modification m = originalMods[i];
145            if (m.hasValue())
146            {
147              final String[] originalValues = m.getValues();
148              final String[] newValues = new String[originalValues.length];
149              for (int j=0; j < originalValues.length; j++)
150              {
151                newValues[j] = processString(originalValues[j]);
152              }
153              newMods[i] = new Modification(m.getModificationType(),
154                   m.getAttributeName(), newValues);
155            }
156            else
157            {
158              newMods[i] = originalMods[i];
159            }
160          }
161    
162          return new LDIFModifyChangeRecord(processString(modRecord.getDN()),
163               newMods, modRecord.getControls());
164        }
165        else if (r instanceof LDIFModifyDNChangeRecord)
166        {
167          final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r;
168          return new LDIFModifyDNChangeRecord(processString(modDNRecord.getDN()),
169               modDNRecord.getNewRDN(), modDNRecord.deleteOldRDN(),
170               processString(modDNRecord.getNewSuperiorDN()),
171               modDNRecord.getControls());
172        }
173        else
174        {
175          // This should never happen.
176          return r;
177        }
178      }
179    
180    
181    
182      /**
183       * Identifies whether the provided string represents a DN that is at or below
184       * the specified source base DN.  If so, then it will be updated to replace
185       * the old base DN with the new base DN.  Otherwise, the original string will
186       * be returned.
187       *
188       * @param  s  The string to process.
189       *
190       * @return  A new string if the provided value was a valid DN at or below the
191       *          source DN, or the original string if it was not a valid DN or was
192       *          not below the source DN.
193       */
194      String processString(final String s)
195      {
196        if (s == null)
197        {
198          return null;
199        }
200    
201        try
202        {
203          final DN dn = new DN(s);
204          if (! dn.isDescendantOf(sourceDN, true))
205          {
206            return s;
207          }
208    
209          final RDN[] originalRDNs = dn.getRDNs();
210          final RDN[] sourceRDNs = sourceDN.getRDNs();
211          final ArrayList<RDN> newRDNs = new ArrayList<RDN>(2*originalRDNs.length);
212          final int numComponentsToKeep = originalRDNs.length - sourceRDNs.length;
213          for (int i=0; i < numComponentsToKeep; i++)
214          {
215            newRDNs.add(originalRDNs[i]);
216          }
217    
218          newRDNs.addAll(targetRDNs);
219          return new DN(newRDNs).toString();
220        }
221        catch (final Exception e)
222        {
223          // This is fine.  The value isn't a DN.
224          return s;
225        }
226      }
227    
228    
229    
230      /**
231       * {@inheritDoc}
232       */
233      public Entry translate(final Entry original, final long firstLineNumber)
234      {
235        return transformEntry(original);
236      }
237    
238    
239    
240      /**
241       * {@inheritDoc}
242       */
243      public LDIFChangeRecord translate(final LDIFChangeRecord original,
244                                        final long firstLineNumber)
245      {
246        return transformChangeRecord(original);
247      }
248    
249    
250    
251      /**
252       * {@inheritDoc}
253       */
254      public Entry translateEntryToWrite(final Entry original)
255      {
256        return transformEntry(original);
257      }
258    
259    
260    
261      /**
262       * {@inheritDoc}
263       */
264      public LDIFChangeRecord translateChangeRecordToWrite(
265                                   final LDIFChangeRecord original)
266      {
267        return transformChangeRecord(original);
268      }
269    }