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