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 }