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