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