001/* 002 * Copyright 2016-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2017 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 */ 021package com.unboundid.ldap.sdk.transformations; 022 023 024 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.Set; 030 031import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule; 032import com.unboundid.ldap.matchingrules.MatchingRule; 033import com.unboundid.ldap.sdk.Attribute; 034import com.unboundid.ldap.sdk.DN; 035import com.unboundid.ldap.sdk.Entry; 036import com.unboundid.ldap.sdk.Modification; 037import com.unboundid.ldap.sdk.RDN; 038import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 039import com.unboundid.ldap.sdk.schema.Schema; 040import com.unboundid.ldif.LDIFAddChangeRecord; 041import com.unboundid.ldif.LDIFChangeRecord; 042import com.unboundid.ldif.LDIFDeleteChangeRecord; 043import com.unboundid.ldif.LDIFModifyChangeRecord; 044import com.unboundid.ldif.LDIFModifyDNChangeRecord; 045import com.unboundid.util.Debug; 046import com.unboundid.util.StaticUtils; 047import com.unboundid.util.ThreadSafety; 048import 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) 058public 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}