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.Arrays; 042import java.util.Collection; 043import java.util.List; 044 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.DN; 047import com.unboundid.ldap.sdk.Entry; 048import com.unboundid.ldap.sdk.Modification; 049import com.unboundid.ldap.sdk.RDN; 050import com.unboundid.ldif.LDIFAddChangeRecord; 051import com.unboundid.ldif.LDIFChangeRecord; 052import com.unboundid.ldif.LDIFDeleteChangeRecord; 053import com.unboundid.ldif.LDIFModifyChangeRecord; 054import com.unboundid.ldif.LDIFModifyDNChangeRecord; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060 061 062/** 063 * This class provides an implementation of an entry and LDIF change record 064 * transformation that will alter DNs at or below a specified base DN to replace 065 * that base DN with a different base DN. This replacement will be applied to 066 * the DNs of entries that are transformed, as well as in any attribute values 067 * that represent DNs at or below the specified base DN. 068 */ 069@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 070public final class MoveSubtreeTransformation 071 implements EntryTransformation, LDIFChangeRecordTransformation 072{ 073 // The source base DN to be replaced. 074 @NotNull private final DN sourceDN; 075 076 // A list of the RDNs in the target base DN. 077 @NotNull private final List<RDN> targetRDNs; 078 079 080 081 /** 082 * Creates a new move subtree transformation with the provided information. 083 * 084 * @param sourceDN The source base DN to be replaced with the target base 085 * DN. It must not be {@code null}. 086 * @param targetDN The target base DN to use to replace the source base DN. 087 * It must not be {@code null}. 088 */ 089 public MoveSubtreeTransformation(@NotNull final DN sourceDN, 090 @NotNull final DN targetDN) 091 { 092 this.sourceDN = sourceDN; 093 094 targetRDNs = Arrays.asList(targetDN.getRDNs()); 095 } 096 097 098 099 /** 100 * {@inheritDoc} 101 */ 102 @Override() 103 @Nullable() 104 public Entry transformEntry(@NotNull final Entry e) 105 { 106 if (e == null) 107 { 108 return null; 109 } 110 111 112 // Iterate through the attributes in the entry and make any appropriate DN 113 // replacements 114 final Collection<Attribute> originalAttributes = e.getAttributes(); 115 final ArrayList<Attribute> newAttributes = 116 new ArrayList<>(originalAttributes.size()); 117 for (final Attribute a : originalAttributes) 118 { 119 final String[] originalValues = a.getValues(); 120 final String[] newValues = new String[originalValues.length]; 121 for (int i=0; i < originalValues.length; i++) 122 { 123 newValues[i] = processString(originalValues[i]); 124 } 125 126 newAttributes.add(new Attribute(a.getName(), newValues)); 127 } 128 129 return new Entry(processString(e.getDN()), newAttributes); 130 } 131 132 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override() 138 @Nullable() 139 public LDIFChangeRecord transformChangeRecord( 140 @NotNull final LDIFChangeRecord r) 141 { 142 if (r == null) 143 { 144 return null; 145 } 146 147 148 if (r instanceof LDIFAddChangeRecord) 149 { 150 // Just use the same processing as for an entry. 151 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r; 152 return new LDIFAddChangeRecord(transformEntry(addRecord.getEntryToAdd()), 153 addRecord.getControls()); 154 } 155 if (r instanceof LDIFDeleteChangeRecord) 156 { 157 return new LDIFDeleteChangeRecord(processString(r.getDN()), 158 r.getControls()); 159 } 160 else if (r instanceof LDIFModifyChangeRecord) 161 { 162 final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r; 163 final Modification[] originalMods = modRecord.getModifications(); 164 final Modification[] newMods = new Modification[originalMods.length]; 165 for (int i=0; i < originalMods.length; i++) 166 { 167 final Modification m = originalMods[i]; 168 if (m.hasValue()) 169 { 170 final String[] originalValues = m.getValues(); 171 final String[] newValues = new String[originalValues.length]; 172 for (int j=0; j < originalValues.length; j++) 173 { 174 newValues[j] = processString(originalValues[j]); 175 } 176 newMods[i] = new Modification(m.getModificationType(), 177 m.getAttributeName(), newValues); 178 } 179 else 180 { 181 newMods[i] = originalMods[i]; 182 } 183 } 184 185 return new LDIFModifyChangeRecord(processString(modRecord.getDN()), 186 newMods, modRecord.getControls()); 187 } 188 else if (r instanceof LDIFModifyDNChangeRecord) 189 { 190 final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r; 191 return new LDIFModifyDNChangeRecord(processString(modDNRecord.getDN()), 192 modDNRecord.getNewRDN(), modDNRecord.deleteOldRDN(), 193 processString(modDNRecord.getNewSuperiorDN()), 194 modDNRecord.getControls()); 195 } 196 else 197 { 198 // This should never happen. 199 return r; 200 } 201 } 202 203 204 205 /** 206 * Identifies whether the provided string represents a DN that is at or below 207 * the specified source base DN. If so, then it will be updated to replace 208 * the old base DN with the new base DN. Otherwise, the original string will 209 * be returned. 210 * 211 * @param s The string to process. 212 * 213 * @return A new string if the provided value was a valid DN at or below the 214 * source DN, or the original string if it was not a valid DN or was 215 * not below the source DN. 216 */ 217 @Nullable() 218 String processString(@Nullable final String s) 219 { 220 if (s == null) 221 { 222 return null; 223 } 224 225 try 226 { 227 final DN dn = new DN(s); 228 if (! dn.isDescendantOf(sourceDN, true)) 229 { 230 return s; 231 } 232 233 final RDN[] originalRDNs = dn.getRDNs(); 234 final RDN[] sourceRDNs = sourceDN.getRDNs(); 235 final ArrayList<RDN> newRDNs = new ArrayList<>(2*originalRDNs.length); 236 final int numComponentsToKeep = originalRDNs.length - sourceRDNs.length; 237 for (int i=0; i < numComponentsToKeep; i++) 238 { 239 newRDNs.add(originalRDNs[i]); 240 } 241 242 newRDNs.addAll(targetRDNs); 243 return new DN(newRDNs).toString(); 244 } 245 catch (final Exception e) 246 { 247 // This is fine. The value isn't a DN. 248 return s; 249 } 250 } 251 252 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override() 258 @Nullable() 259 public Entry translate(@NotNull final Entry original, 260 final long firstLineNumber) 261 { 262 return transformEntry(original); 263 } 264 265 266 267 /** 268 * {@inheritDoc} 269 */ 270 @Override() 271 @Nullable() 272 public LDIFChangeRecord translate(@NotNull final LDIFChangeRecord original, 273 final long firstLineNumber) 274 { 275 return transformChangeRecord(original); 276 } 277 278 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override() 284 @Nullable() 285 public Entry translateEntryToWrite(@NotNull final Entry original) 286 { 287 return transformEntry(original); 288 } 289 290 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override() 296 @Nullable() 297 public LDIFChangeRecord translateChangeRecordToWrite( 298 @NotNull final LDIFChangeRecord original) 299 { 300 return transformChangeRecord(original); 301 } 302}