001/*
002 * Copyright 2016-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2017 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 */
021package com.unboundid.ldap.sdk.transformations;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.List;
029
030import com.unboundid.ldap.sdk.Attribute;
031import com.unboundid.ldap.sdk.DN;
032import com.unboundid.ldap.sdk.Entry;
033import com.unboundid.ldap.sdk.Modification;
034import com.unboundid.ldap.sdk.RDN;
035import com.unboundid.ldif.LDIFAddChangeRecord;
036import com.unboundid.ldif.LDIFChangeRecord;
037import com.unboundid.ldif.LDIFDeleteChangeRecord;
038import com.unboundid.ldif.LDIFModifyChangeRecord;
039import com.unboundid.ldif.LDIFModifyDNChangeRecord;
040import com.unboundid.util.ThreadSafety;
041import 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)
053public 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}