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.Collection;
027 import java.util.Collections;
028 import java.util.HashSet;
029 import java.util.Set;
030
031 import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
032 import com.unboundid.ldap.matchingrules.MatchingRule;
033 import com.unboundid.ldap.sdk.Attribute;
034 import com.unboundid.ldap.sdk.DN;
035 import com.unboundid.ldap.sdk.Entry;
036 import com.unboundid.ldap.sdk.Modification;
037 import com.unboundid.ldap.sdk.RDN;
038 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
039 import com.unboundid.ldap.sdk.schema.Schema;
040 import com.unboundid.ldif.LDIFAddChangeRecord;
041 import com.unboundid.ldif.LDIFChangeRecord;
042 import com.unboundid.ldif.LDIFDeleteChangeRecord;
043 import com.unboundid.ldif.LDIFModifyChangeRecord;
044 import com.unboundid.ldif.LDIFModifyDNChangeRecord;
045 import com.unboundid.util.Debug;
046 import com.unboundid.util.StaticUtils;
047 import com.unboundid.util.ThreadSafety;
048 import com.unboundid.util.ThreadSafetyLevel;
049
050
051
052 /**
053 * This class provides an implementation of an entry and LDIF change record
054 * translator that will rename a specified attribute so that it uses a different
055 * name.
056 */
057 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
058 public final class RenameAttributeTransformation
059 implements EntryTransformation, LDIFChangeRecordTransformation
060 {
061 // Indicates whether to rename attributes in entry DNs.
062 private final boolean renameInDNs;
063
064 // The schema that will be used in processing.
065 private final Schema schema;
066
067 // The names that will be replaced with the target name.
068 private final Set<String> baseSourceNames;
069
070 // The target name that will be used in place of the source name.
071 private final String baseTargetName;
072
073
074
075 /**
076 * Creates a new rename attribute transformation with the provided
077 * information.
078 *
079 * @param schema The schema to use in processing. If this is
080 * {@code null}, a default standard schema will be
081 * used.
082 * @param sourceAttribute The name of the source attribute to be replaced
083 * with the name of the target attribute. It must
084 * not be {@code null}.
085 * @param targetAttribute The name of the target attribute to use in place
086 * of the source attribute. It must not be
087 * {@code null}.
088 * @param renameInDNs Indicates whether to rename attributes contained
089 * in DNs. This includes both in the DN of an entry
090 * to be transformed, but also in the values of
091 * attributes with a DN syntax.
092 */
093 public RenameAttributeTransformation(final Schema schema,
094 final String sourceAttribute,
095 final String targetAttribute,
096 final boolean renameInDNs)
097 {
098 this.renameInDNs = renameInDNs;
099
100
101 // If a schema was provided, then use it. Otherwise, use the default
102 // standard schema.
103 Schema s = schema;
104 if (s == null)
105 {
106 try
107 {
108 s = Schema.getDefaultStandardSchema();
109 }
110 catch (final Exception e)
111 {
112 // This should never happen.
113 Debug.debugException(e);
114 }
115 }
116 this.schema = s;
117
118
119 final HashSet<String> sourceNames = new HashSet<String>(5);
120 final String baseSourceName =
121 StaticUtils.toLowerCase(Attribute.getBaseName(sourceAttribute));
122 sourceNames.add(baseSourceName);
123
124 if (s != null)
125 {
126 final AttributeTypeDefinition at = s.getAttributeType(baseSourceName);
127 if (at != null)
128 {
129 sourceNames.add(StaticUtils.toLowerCase(at.getOID()));
130 for (final String name : at.getNames())
131 {
132 sourceNames.add(StaticUtils.toLowerCase(name));
133 }
134 }
135 }
136 baseSourceNames = Collections.unmodifiableSet(sourceNames);
137
138
139 baseTargetName = Attribute.getBaseName(targetAttribute);
140 }
141
142
143
144 /**
145 * {@inheritDoc}
146 */
147 public Entry transformEntry(final Entry e)
148 {
149 if (e == null)
150 {
151 return null;
152 }
153
154
155 final String newDN;
156 if (renameInDNs)
157 {
158 newDN = replaceDN(e.getDN());
159 }
160 else
161 {
162 newDN = e.getDN();
163 }
164
165
166 // Iterate through the attributes in the entry and make any appropriate name
167 // replacements.
168 final Collection<Attribute> originalAttributes = e.getAttributes();
169 final ArrayList<Attribute> newAttributes =
170 new ArrayList<Attribute>(originalAttributes.size());
171 for (final Attribute a : originalAttributes)
172 {
173 // Determine if we we should rename this attribute.
174 final String newName;
175 final String baseName = StaticUtils.toLowerCase(a.getBaseName());
176 if (baseSourceNames.contains(baseName))
177 {
178 if (a.hasOptions())
179 {
180 final StringBuilder buffer = new StringBuilder();
181 buffer.append(baseTargetName);
182 for (final String option : a.getOptions())
183 {
184 buffer.append(';');
185 buffer.append(option);
186 }
187 newName = buffer.toString();
188 }
189 else
190 {
191 newName = baseTargetName;
192 }
193 }
194 else
195 {
196 newName = a.getName();
197 }
198
199
200 // If we should rename attributes in entry DNs, then see if this
201 // attribute has a DN syntax and if so then process its values.
202 final String[] newValues;
203 if (renameInDNs && (schema != null) &&
204 (MatchingRule.selectEqualityMatchingRule(baseName, schema)
205 instanceof DistinguishedNameMatchingRule))
206 {
207 final String[] originalValues = a.getValues();
208 newValues = new String[originalValues.length];
209 for (int i=0; i < originalValues.length; i++)
210 {
211 newValues[i] = replaceDN(originalValues[i]);
212 }
213 }
214 else
215 {
216 newValues = a.getValues();
217 }
218
219 newAttributes.add(new Attribute(newName, schema, newValues));
220 }
221
222 return new Entry(newDN, newAttributes);
223 }
224
225
226
227 /**
228 * {@inheritDoc}
229 */
230 public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
231 {
232 if (r == null)
233 {
234 return null;
235 }
236
237
238 if (r instanceof LDIFAddChangeRecord)
239 {
240 // Just use the same processing as for an entry.
241 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
242 return new LDIFAddChangeRecord(transformEntry(
243 addRecord.getEntryToAdd()), addRecord.getControls());
244 }
245 if (r instanceof LDIFDeleteChangeRecord)
246 {
247 if (renameInDNs)
248 {
249 return new LDIFDeleteChangeRecord(replaceDN(r.getDN()),
250 r.getControls());
251 }
252 else
253 {
254 return r;
255 }
256 }
257 else if (r instanceof LDIFModifyChangeRecord)
258 {
259 // Determine the new DN for the change record.
260 final String newDN;
261 final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r;
262 if (renameInDNs)
263 {
264 newDN = replaceDN(modRecord.getDN());
265 }
266 else
267 {
268 newDN = modRecord.getDN();
269 }
270
271
272 // Iterate through the attributes and perform the appropriate rename
273 // processing
274 final Modification[] originalMods = modRecord.getModifications();
275 final Modification[] newMods = new Modification[originalMods.length];
276 for (int i=0; i < originalMods.length; i++)
277 {
278 final String newName;
279 final Modification m = originalMods[i];
280 final String baseName = StaticUtils.toLowerCase(
281 Attribute.getBaseName(m.getAttributeName()));
282 if (baseSourceNames.contains(baseName))
283 {
284 final Set<String> options =
285 Attribute.getOptions(m.getAttributeName());
286 if (options.isEmpty())
287 {
288 newName = baseTargetName;
289 }
290 else
291 {
292 final StringBuilder buffer = new StringBuilder();
293 buffer.append(baseTargetName);
294 for (final String option : options)
295 {
296 buffer.append(';');
297 buffer.append(option);
298 }
299 newName = buffer.toString();
300 }
301 }
302 else
303 {
304 newName = m.getAttributeName();
305 }
306
307 final String[] newValues;
308 if (renameInDNs && (schema != null) &&
309 (MatchingRule.selectEqualityMatchingRule(baseName, schema)
310 instanceof DistinguishedNameMatchingRule))
311 {
312 final String[] originalValues = m.getValues();
313 newValues = new String[originalValues.length];
314 for (int j=0; j < originalValues.length; j++)
315 {
316 newValues[j] = replaceDN(originalValues[j]);
317 }
318 }
319 else
320 {
321 newValues = m.getValues();
322 }
323
324 newMods[i] = new Modification(m.getModificationType(), newName,
325 newValues);
326 }
327
328 return new LDIFModifyChangeRecord(newDN, newMods,
329 modRecord.getControls());
330 }
331 else if (r instanceof LDIFModifyDNChangeRecord)
332 {
333 if (renameInDNs)
334 {
335 final LDIFModifyDNChangeRecord modDNRecord =
336 (LDIFModifyDNChangeRecord) r;
337 return new LDIFModifyDNChangeRecord(replaceDN(modDNRecord.getDN()),
338 replaceDN(modDNRecord.getNewRDN()), modDNRecord.deleteOldRDN(),
339 replaceDN(modDNRecord.getNewSuperiorDN()),
340 modDNRecord.getControls());
341 }
342 else
343 {
344 return r;
345 }
346 }
347 else
348 {
349 // This should never happen.
350 return r;
351 }
352 }
353
354
355
356 /**
357 * Makes any appropriate attribute replacements in the provided DN.
358 *
359 * @param dn The DN to process.
360 *
361 * @return The DN with any appropriate replacements.
362 */
363 private String replaceDN(final String dn)
364 {
365 try
366 {
367 final DN parsedDN = new DN(dn);
368 final RDN[] originalRDNs = parsedDN.getRDNs();
369 final RDN[] newRDNs = new RDN[originalRDNs.length];
370 for (int i=0; i < originalRDNs.length; i++)
371 {
372 final String[] originalNames = originalRDNs[i].getAttributeNames();
373 final String[] newNames = new String[originalNames.length];
374 for (int j=0; j < originalNames.length; j++)
375 {
376 if (baseSourceNames.contains(
377 StaticUtils.toLowerCase(originalNames[j])))
378 {
379 newNames[j] = baseTargetName;
380 }
381 else
382 {
383 newNames[j] = originalNames[j];
384 }
385 }
386 newRDNs[i] =
387 new RDN(newNames, originalRDNs[i].getByteArrayAttributeValues());
388 }
389
390 return new DN(newRDNs).toString();
391 }
392 catch (final Exception e)
393 {
394 Debug.debugException(e);
395 return dn;
396 }
397 }
398
399
400
401 /**
402 * {@inheritDoc}
403 */
404 public Entry translate(final Entry original, final long firstLineNumber)
405 {
406 return transformEntry(original);
407 }
408
409
410
411 /**
412 * {@inheritDoc}
413 */
414 public LDIFChangeRecord translate(final LDIFChangeRecord original,
415 final long firstLineNumber)
416 {
417 return transformChangeRecord(original);
418 }
419
420
421
422 /**
423 * {@inheritDoc}
424 */
425 public Entry translateEntryToWrite(final Entry original)
426 {
427 return transformEntry(original);
428 }
429
430
431
432 /**
433 * {@inheritDoc}
434 */
435 public LDIFChangeRecord translateChangeRecordToWrite(
436 final LDIFChangeRecord original)
437 {
438 return transformChangeRecord(original);
439 }
440 }