001/*
002 * Copyright 2016-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-2020 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-2020 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.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057
058
059
060/**
061 * This class provides an implementation of an entry and LDIF change record
062 * transformation that will alter DNs at or below a specified base DN to replace
063 * that base DN with a different base DN.  This replacement will be applied to
064 * the DNs of entries that are transformed, as well as in any attribute values
065 * that represent DNs at or below the specified base DN.
066 */
067@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
068public final class MoveSubtreeTransformation
069       implements EntryTransformation, LDIFChangeRecordTransformation
070{
071  // The source base DN to be replaced.
072  private final DN sourceDN;
073
074  // A list of the RDNs in the target base DN.
075  private final List<RDN> targetRDNs;
076
077
078
079  /**
080   * Creates a new move subtree transformation with the provided information.
081   *
082   * @param  sourceDN  The source base DN to be replaced with the target base
083   *                   DN.  It must not be {@code null}.
084   * @param  targetDN  The target base DN to use to replace the source base DN.
085   *                   It must not be {@code null}.
086   */
087  public MoveSubtreeTransformation(final DN sourceDN, final DN targetDN)
088  {
089    this.sourceDN = sourceDN;
090
091    targetRDNs = Arrays.asList(targetDN.getRDNs());
092  }
093
094
095
096  /**
097   * {@inheritDoc}
098   */
099  @Override()
100  public Entry transformEntry(final Entry e)
101  {
102    if (e == null)
103    {
104      return null;
105    }
106
107
108    // Iterate through the attributes in the entry and make any appropriate DN
109    // replacements
110    final Collection<Attribute> originalAttributes = e.getAttributes();
111    final ArrayList<Attribute> newAttributes =
112         new ArrayList<>(originalAttributes.size());
113    for (final Attribute a : originalAttributes)
114    {
115      final String[] originalValues = a.getValues();
116      final String[] newValues = new String[originalValues.length];
117      for (int i=0; i < originalValues.length; i++)
118      {
119        newValues[i] = processString(originalValues[i]);
120      }
121
122      newAttributes.add(new Attribute(a.getName(), newValues));
123    }
124
125    return new Entry(processString(e.getDN()), newAttributes);
126  }
127
128
129
130  /**
131   * {@inheritDoc}
132   */
133  @Override()
134  public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
135  {
136    if (r == null)
137    {
138      return null;
139    }
140
141
142    if (r instanceof LDIFAddChangeRecord)
143    {
144      // Just use the same processing as for an entry.
145      final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
146      return new LDIFAddChangeRecord(transformEntry(addRecord.getEntryToAdd()),
147           addRecord.getControls());
148    }
149    if (r instanceof LDIFDeleteChangeRecord)
150    {
151      return new LDIFDeleteChangeRecord(processString(r.getDN()),
152           r.getControls());
153    }
154    else if (r instanceof LDIFModifyChangeRecord)
155    {
156      final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r;
157      final Modification[] originalMods = modRecord.getModifications();
158      final Modification[] newMods = new Modification[originalMods.length];
159      for (int i=0; i < originalMods.length; i++)
160      {
161        final Modification m = originalMods[i];
162        if (m.hasValue())
163        {
164          final String[] originalValues = m.getValues();
165          final String[] newValues = new String[originalValues.length];
166          for (int j=0; j < originalValues.length; j++)
167          {
168            newValues[j] = processString(originalValues[j]);
169          }
170          newMods[i] = new Modification(m.getModificationType(),
171               m.getAttributeName(), newValues);
172        }
173        else
174        {
175          newMods[i] = originalMods[i];
176        }
177      }
178
179      return new LDIFModifyChangeRecord(processString(modRecord.getDN()),
180           newMods, modRecord.getControls());
181    }
182    else if (r instanceof LDIFModifyDNChangeRecord)
183    {
184      final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r;
185      return new LDIFModifyDNChangeRecord(processString(modDNRecord.getDN()),
186           modDNRecord.getNewRDN(), modDNRecord.deleteOldRDN(),
187           processString(modDNRecord.getNewSuperiorDN()),
188           modDNRecord.getControls());
189    }
190    else
191    {
192      // This should never happen.
193      return r;
194    }
195  }
196
197
198
199  /**
200   * Identifies whether the provided string represents a DN that is at or below
201   * the specified source base DN.  If so, then it will be updated to replace
202   * the old base DN with the new base DN.  Otherwise, the original string will
203   * be returned.
204   *
205   * @param  s  The string to process.
206   *
207   * @return  A new string if the provided value was a valid DN at or below the
208   *          source DN, or the original string if it was not a valid DN or was
209   *          not below the source DN.
210   */
211  String processString(final String s)
212  {
213    if (s == null)
214    {
215      return null;
216    }
217
218    try
219    {
220      final DN dn = new DN(s);
221      if (! dn.isDescendantOf(sourceDN, true))
222      {
223        return s;
224      }
225
226      final RDN[] originalRDNs = dn.getRDNs();
227      final RDN[] sourceRDNs = sourceDN.getRDNs();
228      final ArrayList<RDN> newRDNs = new ArrayList<>(2*originalRDNs.length);
229      final int numComponentsToKeep = originalRDNs.length - sourceRDNs.length;
230      for (int i=0; i < numComponentsToKeep; i++)
231      {
232        newRDNs.add(originalRDNs[i]);
233      }
234
235      newRDNs.addAll(targetRDNs);
236      return new DN(newRDNs).toString();
237    }
238    catch (final Exception e)
239    {
240      // This is fine.  The value isn't a DN.
241      return s;
242    }
243  }
244
245
246
247  /**
248   * {@inheritDoc}
249   */
250  @Override()
251  public Entry translate(final Entry original, final long firstLineNumber)
252  {
253    return transformEntry(original);
254  }
255
256
257
258  /**
259   * {@inheritDoc}
260   */
261  @Override()
262  public LDIFChangeRecord translate(final LDIFChangeRecord original,
263                                    final long firstLineNumber)
264  {
265    return transformChangeRecord(original);
266  }
267
268
269
270  /**
271   * {@inheritDoc}
272   */
273  @Override()
274  public Entry translateEntryToWrite(final Entry original)
275  {
276    return transformEntry(original);
277  }
278
279
280
281  /**
282   * {@inheritDoc}
283   */
284  @Override()
285  public LDIFChangeRecord translateChangeRecordToWrite(
286                               final LDIFChangeRecord original)
287  {
288    return transformChangeRecord(original);
289  }
290}