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