001/*
002 * Copyright 2016-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2019 Ping Identity Corporation
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  @Override()
085  public Entry transformEntry(final Entry e)
086  {
087    if (e == null)
088    {
089      return null;
090    }
091
092
093    // Iterate through the attributes in the entry and make any appropriate DN
094    // replacements
095    final Collection<Attribute> originalAttributes = e.getAttributes();
096    final ArrayList<Attribute> newAttributes =
097         new ArrayList<>(originalAttributes.size());
098    for (final Attribute a : originalAttributes)
099    {
100      final String[] originalValues = a.getValues();
101      final String[] newValues = new String[originalValues.length];
102      for (int i=0; i < originalValues.length; i++)
103      {
104        newValues[i] = processString(originalValues[i]);
105      }
106
107      newAttributes.add(new Attribute(a.getName(), newValues));
108    }
109
110    return new Entry(processString(e.getDN()), newAttributes);
111  }
112
113
114
115  /**
116   * {@inheritDoc}
117   */
118  @Override()
119  public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
120  {
121    if (r == null)
122    {
123      return null;
124    }
125
126
127    if (r instanceof LDIFAddChangeRecord)
128    {
129      // Just use the same processing as for an entry.
130      final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
131      return new LDIFAddChangeRecord(transformEntry(addRecord.getEntryToAdd()),
132           addRecord.getControls());
133    }
134    if (r instanceof LDIFDeleteChangeRecord)
135    {
136      return new LDIFDeleteChangeRecord(processString(r.getDN()),
137           r.getControls());
138    }
139    else if (r instanceof LDIFModifyChangeRecord)
140    {
141      final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r;
142      final Modification[] originalMods = modRecord.getModifications();
143      final Modification[] newMods = new Modification[originalMods.length];
144      for (int i=0; i < originalMods.length; i++)
145      {
146        final Modification m = originalMods[i];
147        if (m.hasValue())
148        {
149          final String[] originalValues = m.getValues();
150          final String[] newValues = new String[originalValues.length];
151          for (int j=0; j < originalValues.length; j++)
152          {
153            newValues[j] = processString(originalValues[j]);
154          }
155          newMods[i] = new Modification(m.getModificationType(),
156               m.getAttributeName(), newValues);
157        }
158        else
159        {
160          newMods[i] = originalMods[i];
161        }
162      }
163
164      return new LDIFModifyChangeRecord(processString(modRecord.getDN()),
165           newMods, modRecord.getControls());
166    }
167    else if (r instanceof LDIFModifyDNChangeRecord)
168    {
169      final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r;
170      return new LDIFModifyDNChangeRecord(processString(modDNRecord.getDN()),
171           modDNRecord.getNewRDN(), modDNRecord.deleteOldRDN(),
172           processString(modDNRecord.getNewSuperiorDN()),
173           modDNRecord.getControls());
174    }
175    else
176    {
177      // This should never happen.
178      return r;
179    }
180  }
181
182
183
184  /**
185   * Identifies whether the provided string represents a DN that is at or below
186   * the specified source base DN.  If so, then it will be updated to replace
187   * the old base DN with the new base DN.  Otherwise, the original string will
188   * be returned.
189   *
190   * @param  s  The string to process.
191   *
192   * @return  A new string if the provided value was a valid DN at or below the
193   *          source DN, or the original string if it was not a valid DN or was
194   *          not below the source DN.
195   */
196  String processString(final String s)
197  {
198    if (s == null)
199    {
200      return null;
201    }
202
203    try
204    {
205      final DN dn = new DN(s);
206      if (! dn.isDescendantOf(sourceDN, true))
207      {
208        return s;
209      }
210
211      final RDN[] originalRDNs = dn.getRDNs();
212      final RDN[] sourceRDNs = sourceDN.getRDNs();
213      final ArrayList<RDN> newRDNs = new ArrayList<>(2*originalRDNs.length);
214      final int numComponentsToKeep = originalRDNs.length - sourceRDNs.length;
215      for (int i=0; i < numComponentsToKeep; i++)
216      {
217        newRDNs.add(originalRDNs[i]);
218      }
219
220      newRDNs.addAll(targetRDNs);
221      return new DN(newRDNs).toString();
222    }
223    catch (final Exception e)
224    {
225      // This is fine.  The value isn't a DN.
226      return s;
227    }
228  }
229
230
231
232  /**
233   * {@inheritDoc}
234   */
235  @Override()
236  public Entry translate(final Entry original, final long firstLineNumber)
237  {
238    return transformEntry(original);
239  }
240
241
242
243  /**
244   * {@inheritDoc}
245   */
246  @Override()
247  public LDIFChangeRecord translate(final LDIFChangeRecord original,
248                                    final long firstLineNumber)
249  {
250    return transformChangeRecord(original);
251  }
252
253
254
255  /**
256   * {@inheritDoc}
257   */
258  @Override()
259  public Entry translateEntryToWrite(final Entry original)
260  {
261    return transformEntry(original);
262  }
263
264
265
266  /**
267   * {@inheritDoc}
268   */
269  @Override()
270  public LDIFChangeRecord translateChangeRecordToWrite(
271                               final LDIFChangeRecord original)
272  {
273    return transformChangeRecord(original);
274  }
275}