001/* 002 * Copyright 2016-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2019 Ping Identity Corporation 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 = 120 new HashSet<>(StaticUtils.computeMapCapacity(5)); 121 final String baseSourceName = 122 StaticUtils.toLowerCase(Attribute.getBaseName(sourceAttribute)); 123 sourceNames.add(baseSourceName); 124 125 if (s != null) 126 { 127 final AttributeTypeDefinition at = s.getAttributeType(baseSourceName); 128 if (at != null) 129 { 130 sourceNames.add(StaticUtils.toLowerCase(at.getOID())); 131 for (final String name : at.getNames()) 132 { 133 sourceNames.add(StaticUtils.toLowerCase(name)); 134 } 135 } 136 } 137 baseSourceNames = Collections.unmodifiableSet(sourceNames); 138 139 140 baseTargetName = Attribute.getBaseName(targetAttribute); 141 } 142 143 144 145 /** 146 * {@inheritDoc} 147 */ 148 @Override() 149 public Entry transformEntry(final Entry e) 150 { 151 if (e == null) 152 { 153 return null; 154 } 155 156 157 final String newDN; 158 if (renameInDNs) 159 { 160 newDN = replaceDN(e.getDN()); 161 } 162 else 163 { 164 newDN = e.getDN(); 165 } 166 167 168 // Iterate through the attributes in the entry and make any appropriate name 169 // replacements. 170 final Collection<Attribute> originalAttributes = e.getAttributes(); 171 final ArrayList<Attribute> newAttributes = 172 new ArrayList<>(originalAttributes.size()); 173 for (final Attribute a : originalAttributes) 174 { 175 // Determine if we we should rename this attribute. 176 final String newName; 177 final String baseName = StaticUtils.toLowerCase(a.getBaseName()); 178 if (baseSourceNames.contains(baseName)) 179 { 180 if (a.hasOptions()) 181 { 182 final StringBuilder buffer = new StringBuilder(); 183 buffer.append(baseTargetName); 184 for (final String option : a.getOptions()) 185 { 186 buffer.append(';'); 187 buffer.append(option); 188 } 189 newName = buffer.toString(); 190 } 191 else 192 { 193 newName = baseTargetName; 194 } 195 } 196 else 197 { 198 newName = a.getName(); 199 } 200 201 202 // If we should rename attributes in entry DNs, then see if this 203 // attribute has a DN syntax and if so then process its values. 204 final String[] newValues; 205 if (renameInDNs && (schema != null) && 206 (MatchingRule.selectEqualityMatchingRule(baseName, schema) 207 instanceof DistinguishedNameMatchingRule)) 208 { 209 final String[] originalValues = a.getValues(); 210 newValues = new String[originalValues.length]; 211 for (int i=0; i < originalValues.length; i++) 212 { 213 newValues[i] = replaceDN(originalValues[i]); 214 } 215 } 216 else 217 { 218 newValues = a.getValues(); 219 } 220 221 newAttributes.add(new Attribute(newName, schema, newValues)); 222 } 223 224 return new Entry(newDN, newAttributes); 225 } 226 227 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override() 233 public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r) 234 { 235 if (r == null) 236 { 237 return null; 238 } 239 240 241 if (r instanceof LDIFAddChangeRecord) 242 { 243 // Just use the same processing as for an entry. 244 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r; 245 return new LDIFAddChangeRecord(transformEntry( 246 addRecord.getEntryToAdd()), addRecord.getControls()); 247 } 248 if (r instanceof LDIFDeleteChangeRecord) 249 { 250 if (renameInDNs) 251 { 252 return new LDIFDeleteChangeRecord(replaceDN(r.getDN()), 253 r.getControls()); 254 } 255 else 256 { 257 return r; 258 } 259 } 260 else if (r instanceof LDIFModifyChangeRecord) 261 { 262 // Determine the new DN for the change record. 263 final String newDN; 264 final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r; 265 if (renameInDNs) 266 { 267 newDN = replaceDN(modRecord.getDN()); 268 } 269 else 270 { 271 newDN = modRecord.getDN(); 272 } 273 274 275 // Iterate through the attributes and perform the appropriate rename 276 // processing 277 final Modification[] originalMods = modRecord.getModifications(); 278 final Modification[] newMods = new Modification[originalMods.length]; 279 for (int i=0; i < originalMods.length; i++) 280 { 281 final String newName; 282 final Modification m = originalMods[i]; 283 final String baseName = StaticUtils.toLowerCase( 284 Attribute.getBaseName(m.getAttributeName())); 285 if (baseSourceNames.contains(baseName)) 286 { 287 final Set<String> options = 288 Attribute.getOptions(m.getAttributeName()); 289 if (options.isEmpty()) 290 { 291 newName = baseTargetName; 292 } 293 else 294 { 295 final StringBuilder buffer = new StringBuilder(); 296 buffer.append(baseTargetName); 297 for (final String option : options) 298 { 299 buffer.append(';'); 300 buffer.append(option); 301 } 302 newName = buffer.toString(); 303 } 304 } 305 else 306 { 307 newName = m.getAttributeName(); 308 } 309 310 final String[] newValues; 311 if (renameInDNs && (schema != null) && 312 (MatchingRule.selectEqualityMatchingRule(baseName, schema) 313 instanceof DistinguishedNameMatchingRule)) 314 { 315 final String[] originalValues = m.getValues(); 316 newValues = new String[originalValues.length]; 317 for (int j=0; j < originalValues.length; j++) 318 { 319 newValues[j] = replaceDN(originalValues[j]); 320 } 321 } 322 else 323 { 324 newValues = m.getValues(); 325 } 326 327 newMods[i] = new Modification(m.getModificationType(), newName, 328 newValues); 329 } 330 331 return new LDIFModifyChangeRecord(newDN, newMods, 332 modRecord.getControls()); 333 } 334 else if (r instanceof LDIFModifyDNChangeRecord) 335 { 336 if (renameInDNs) 337 { 338 final LDIFModifyDNChangeRecord modDNRecord = 339 (LDIFModifyDNChangeRecord) r; 340 return new LDIFModifyDNChangeRecord(replaceDN(modDNRecord.getDN()), 341 replaceDN(modDNRecord.getNewRDN()), modDNRecord.deleteOldRDN(), 342 replaceDN(modDNRecord.getNewSuperiorDN()), 343 modDNRecord.getControls()); 344 } 345 else 346 { 347 return r; 348 } 349 } 350 else 351 { 352 // This should never happen. 353 return r; 354 } 355 } 356 357 358 359 /** 360 * Makes any appropriate attribute replacements in the provided DN. 361 * 362 * @param dn The DN to process. 363 * 364 * @return The DN with any appropriate replacements. 365 */ 366 private String replaceDN(final String dn) 367 { 368 try 369 { 370 final DN parsedDN = new DN(dn); 371 final RDN[] originalRDNs = parsedDN.getRDNs(); 372 final RDN[] newRDNs = new RDN[originalRDNs.length]; 373 for (int i=0; i < originalRDNs.length; i++) 374 { 375 final String[] originalNames = originalRDNs[i].getAttributeNames(); 376 final String[] newNames = new String[originalNames.length]; 377 for (int j=0; j < originalNames.length; j++) 378 { 379 if (baseSourceNames.contains( 380 StaticUtils.toLowerCase(originalNames[j]))) 381 { 382 newNames[j] = baseTargetName; 383 } 384 else 385 { 386 newNames[j] = originalNames[j]; 387 } 388 } 389 newRDNs[i] = 390 new RDN(newNames, originalRDNs[i].getByteArrayAttributeValues()); 391 } 392 393 return new DN(newRDNs).toString(); 394 } 395 catch (final Exception e) 396 { 397 Debug.debugException(e); 398 return dn; 399 } 400 } 401 402 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override() 408 public Entry translate(final Entry original, final long firstLineNumber) 409 { 410 return transformEntry(original); 411 } 412 413 414 415 /** 416 * {@inheritDoc} 417 */ 418 @Override() 419 public LDIFChangeRecord translate(final LDIFChangeRecord original, 420 final long firstLineNumber) 421 { 422 return transformChangeRecord(original); 423 } 424 425 426 427 /** 428 * {@inheritDoc} 429 */ 430 @Override() 431 public Entry translateEntryToWrite(final Entry original) 432 { 433 return transformEntry(original); 434 } 435 436 437 438 /** 439 * {@inheritDoc} 440 */ 441 @Override() 442 public LDIFChangeRecord translateChangeRecordToWrite( 443 final LDIFChangeRecord original) 444 { 445 return transformChangeRecord(original); 446 } 447}