001/* 002 * Copyright 2016-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2016-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.transformations; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.Set; 045 046import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule; 047import com.unboundid.ldap.matchingrules.MatchingRule; 048import com.unboundid.ldap.sdk.Attribute; 049import com.unboundid.ldap.sdk.DN; 050import com.unboundid.ldap.sdk.Entry; 051import com.unboundid.ldap.sdk.Modification; 052import com.unboundid.ldap.sdk.RDN; 053import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 054import com.unboundid.ldap.sdk.schema.Schema; 055import com.unboundid.ldif.LDIFAddChangeRecord; 056import com.unboundid.ldif.LDIFChangeRecord; 057import com.unboundid.ldif.LDIFDeleteChangeRecord; 058import com.unboundid.ldif.LDIFModifyChangeRecord; 059import com.unboundid.ldif.LDIFModifyDNChangeRecord; 060import com.unboundid.util.Debug; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.StaticUtils; 064import com.unboundid.util.ThreadSafety; 065import com.unboundid.util.ThreadSafetyLevel; 066 067 068 069/** 070 * This class provides an implementation of an entry and LDIF change record 071 * translator that will rename a specified attribute so that it uses a different 072 * name. 073 */ 074@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 075public final class RenameAttributeTransformation 076 implements EntryTransformation, LDIFChangeRecordTransformation 077{ 078 // Indicates whether to rename attributes in entry DNs. 079 private final boolean renameInDNs; 080 081 // The schema that will be used in processing. 082 @Nullable private final Schema schema; 083 084 // The names that will be replaced with the target name. 085 @NotNull private final Set<String> baseSourceNames; 086 087 // The target name that will be used in place of the source name. 088 @NotNull private final String baseTargetName; 089 090 091 092 /** 093 * Creates a new rename attribute transformation with the provided 094 * information. 095 * 096 * @param schema The schema to use in processing. If this is 097 * {@code null}, a default standard schema will be 098 * used. 099 * @param sourceAttribute The name of the source attribute to be replaced 100 * with the name of the target attribute. It must 101 * not be {@code null}. 102 * @param targetAttribute The name of the target attribute to use in place 103 * of the source attribute. It must not be 104 * {@code null}. 105 * @param renameInDNs Indicates whether to rename attributes contained 106 * in DNs. This includes both in the DN of an entry 107 * to be transformed, but also in the values of 108 * attributes with a DN syntax. 109 */ 110 public RenameAttributeTransformation(@Nullable final Schema schema, 111 @NotNull final String sourceAttribute, 112 @NotNull final String targetAttribute, 113 final boolean renameInDNs) 114 { 115 this.renameInDNs = renameInDNs; 116 117 118 // If a schema was provided, then use it. Otherwise, use the default 119 // standard schema. 120 Schema s = schema; 121 if (s == null) 122 { 123 try 124 { 125 s = Schema.getDefaultStandardSchema(); 126 } 127 catch (final Exception e) 128 { 129 // This should never happen. 130 Debug.debugException(e); 131 } 132 } 133 this.schema = s; 134 135 136 final HashSet<String> sourceNames = 137 new HashSet<>(StaticUtils.computeMapCapacity(5)); 138 final String baseSourceName = 139 StaticUtils.toLowerCase(Attribute.getBaseName(sourceAttribute)); 140 sourceNames.add(baseSourceName); 141 142 if (s != null) 143 { 144 final AttributeTypeDefinition at = s.getAttributeType(baseSourceName); 145 if (at != null) 146 { 147 sourceNames.add(StaticUtils.toLowerCase(at.getOID())); 148 for (final String name : at.getNames()) 149 { 150 sourceNames.add(StaticUtils.toLowerCase(name)); 151 } 152 } 153 } 154 baseSourceNames = Collections.unmodifiableSet(sourceNames); 155 156 157 baseTargetName = Attribute.getBaseName(targetAttribute); 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override() 166 @Nullable() 167 public Entry transformEntry(@NotNull final Entry e) 168 { 169 if (e == null) 170 { 171 return null; 172 } 173 174 175 final String newDN; 176 if (renameInDNs) 177 { 178 newDN = replaceDN(e.getDN()); 179 } 180 else 181 { 182 newDN = e.getDN(); 183 } 184 185 186 // Iterate through the attributes in the entry and make any appropriate name 187 // replacements. 188 final Collection<Attribute> originalAttributes = e.getAttributes(); 189 final ArrayList<Attribute> newAttributes = 190 new ArrayList<>(originalAttributes.size()); 191 for (final Attribute a : originalAttributes) 192 { 193 // Determine if we we should rename this attribute. 194 final String newName; 195 final String baseName = StaticUtils.toLowerCase(a.getBaseName()); 196 if (baseSourceNames.contains(baseName)) 197 { 198 if (a.hasOptions()) 199 { 200 final StringBuilder buffer = new StringBuilder(); 201 buffer.append(baseTargetName); 202 for (final String option : a.getOptions()) 203 { 204 buffer.append(';'); 205 buffer.append(option); 206 } 207 newName = buffer.toString(); 208 } 209 else 210 { 211 newName = baseTargetName; 212 } 213 } 214 else 215 { 216 newName = a.getName(); 217 } 218 219 220 // If we should rename attributes in entry DNs, then see if this 221 // attribute has a DN syntax and if so then process its values. 222 final String[] newValues; 223 if (renameInDNs && (schema != null) && 224 (MatchingRule.selectEqualityMatchingRule(baseName, schema) 225 instanceof DistinguishedNameMatchingRule)) 226 { 227 final String[] originalValues = a.getValues(); 228 newValues = new String[originalValues.length]; 229 for (int i=0; i < originalValues.length; i++) 230 { 231 newValues[i] = replaceDN(originalValues[i]); 232 } 233 } 234 else 235 { 236 newValues = a.getValues(); 237 } 238 239 newAttributes.add(new Attribute(newName, schema, newValues)); 240 } 241 242 return new Entry(newDN, newAttributes); 243 } 244 245 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override() 251 @Nullable() 252 public LDIFChangeRecord transformChangeRecord( 253 @NotNull final LDIFChangeRecord r) 254 { 255 if (r == null) 256 { 257 return null; 258 } 259 260 261 if (r instanceof LDIFAddChangeRecord) 262 { 263 // Just use the same processing as for an entry. 264 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r; 265 return new LDIFAddChangeRecord(transformEntry( 266 addRecord.getEntryToAdd()), addRecord.getControls()); 267 } 268 if (r instanceof LDIFDeleteChangeRecord) 269 { 270 if (renameInDNs) 271 { 272 return new LDIFDeleteChangeRecord(replaceDN(r.getDN()), 273 r.getControls()); 274 } 275 else 276 { 277 return r; 278 } 279 } 280 else if (r instanceof LDIFModifyChangeRecord) 281 { 282 // Determine the new DN for the change record. 283 final String newDN; 284 final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r; 285 if (renameInDNs) 286 { 287 newDN = replaceDN(modRecord.getDN()); 288 } 289 else 290 { 291 newDN = modRecord.getDN(); 292 } 293 294 295 // Iterate through the attributes and perform the appropriate rename 296 // processing 297 final Modification[] originalMods = modRecord.getModifications(); 298 final Modification[] newMods = new Modification[originalMods.length]; 299 for (int i=0; i < originalMods.length; i++) 300 { 301 final String newName; 302 final Modification m = originalMods[i]; 303 final String baseName = StaticUtils.toLowerCase( 304 Attribute.getBaseName(m.getAttributeName())); 305 if (baseSourceNames.contains(baseName)) 306 { 307 final Set<String> options = 308 Attribute.getOptions(m.getAttributeName()); 309 if (options.isEmpty()) 310 { 311 newName = baseTargetName; 312 } 313 else 314 { 315 final StringBuilder buffer = new StringBuilder(); 316 buffer.append(baseTargetName); 317 for (final String option : options) 318 { 319 buffer.append(';'); 320 buffer.append(option); 321 } 322 newName = buffer.toString(); 323 } 324 } 325 else 326 { 327 newName = m.getAttributeName(); 328 } 329 330 final String[] newValues; 331 if (renameInDNs && (schema != null) && 332 (MatchingRule.selectEqualityMatchingRule(baseName, schema) 333 instanceof DistinguishedNameMatchingRule)) 334 { 335 final String[] originalValues = m.getValues(); 336 newValues = new String[originalValues.length]; 337 for (int j=0; j < originalValues.length; j++) 338 { 339 newValues[j] = replaceDN(originalValues[j]); 340 } 341 } 342 else 343 { 344 newValues = m.getValues(); 345 } 346 347 newMods[i] = new Modification(m.getModificationType(), newName, 348 newValues); 349 } 350 351 return new LDIFModifyChangeRecord(newDN, newMods, 352 modRecord.getControls()); 353 } 354 else if (r instanceof LDIFModifyDNChangeRecord) 355 { 356 if (renameInDNs) 357 { 358 final LDIFModifyDNChangeRecord modDNRecord = 359 (LDIFModifyDNChangeRecord) r; 360 return new LDIFModifyDNChangeRecord(replaceDN(modDNRecord.getDN()), 361 replaceDN(modDNRecord.getNewRDN()), modDNRecord.deleteOldRDN(), 362 replaceDN(modDNRecord.getNewSuperiorDN()), 363 modDNRecord.getControls()); 364 } 365 else 366 { 367 return r; 368 } 369 } 370 else 371 { 372 // This should never happen. 373 return r; 374 } 375 } 376 377 378 379 /** 380 * Makes any appropriate attribute replacements in the provided DN. 381 * 382 * @param dn The DN to process. 383 * 384 * @return The DN with any appropriate replacements. 385 */ 386 @NotNull() 387 private String replaceDN(@NotNull final String dn) 388 { 389 try 390 { 391 final DN parsedDN = new DN(dn); 392 final RDN[] originalRDNs = parsedDN.getRDNs(); 393 final RDN[] newRDNs = new RDN[originalRDNs.length]; 394 for (int i=0; i < originalRDNs.length; i++) 395 { 396 final String[] originalNames = originalRDNs[i].getAttributeNames(); 397 final String[] newNames = new String[originalNames.length]; 398 for (int j=0; j < originalNames.length; j++) 399 { 400 if (baseSourceNames.contains( 401 StaticUtils.toLowerCase(originalNames[j]))) 402 { 403 newNames[j] = baseTargetName; 404 } 405 else 406 { 407 newNames[j] = originalNames[j]; 408 } 409 } 410 newRDNs[i] = 411 new RDN(newNames, originalRDNs[i].getByteArrayAttributeValues()); 412 } 413 414 return new DN(newRDNs).toString(); 415 } 416 catch (final Exception e) 417 { 418 Debug.debugException(e); 419 return dn; 420 } 421 } 422 423 424 425 /** 426 * {@inheritDoc} 427 */ 428 @Override() 429 @Nullable() 430 public Entry translate(@NotNull final Entry original, 431 final long firstLineNumber) 432 { 433 return transformEntry(original); 434 } 435 436 437 438 /** 439 * {@inheritDoc} 440 */ 441 @Override() 442 @Nullable() 443 public LDIFChangeRecord translate(@NotNull final LDIFChangeRecord original, 444 final long firstLineNumber) 445 { 446 return transformChangeRecord(original); 447 } 448 449 450 451 /** 452 * {@inheritDoc} 453 */ 454 @Override() 455 @Nullable() 456 public Entry translateEntryToWrite(@NotNull final Entry original) 457 { 458 return transformEntry(original); 459 } 460 461 462 463 /** 464 * {@inheritDoc} 465 */ 466 @Override() 467 @Nullable() 468 public LDIFChangeRecord translateChangeRecordToWrite( 469 @NotNull final LDIFChangeRecord original) 470 { 471 return transformChangeRecord(original); 472 } 473}