001/* 002 * Copyright 2016-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2017 UnboundID Corp. 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 public Entry transformEntry(final Entry e) 085 { 086 if (e == null) 087 { 088 return null; 089 } 090 091 092 // Iterate through the attributes in the entry and make any appropriate DN 093 // replacements 094 final Collection<Attribute> originalAttributes = e.getAttributes(); 095 final ArrayList<Attribute> newAttributes = 096 new ArrayList<Attribute>(originalAttributes.size()); 097 for (final Attribute a : originalAttributes) 098 { 099 final String[] originalValues = a.getValues(); 100 final String[] newValues = new String[originalValues.length]; 101 for (int i=0; i < originalValues.length; i++) 102 { 103 newValues[i] = processString(originalValues[i]); 104 } 105 106 newAttributes.add(new Attribute(a.getName(), newValues)); 107 } 108 109 return new Entry(processString(e.getDN()), newAttributes); 110 } 111 112 113 114 /** 115 * {@inheritDoc} 116 */ 117 public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r) 118 { 119 if (r == null) 120 { 121 return null; 122 } 123 124 125 if (r instanceof LDIFAddChangeRecord) 126 { 127 // Just use the same processing as for an entry. 128 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r; 129 return new LDIFAddChangeRecord(transformEntry(addRecord.getEntryToAdd()), 130 addRecord.getControls()); 131 } 132 if (r instanceof LDIFDeleteChangeRecord) 133 { 134 return new LDIFDeleteChangeRecord(processString(r.getDN()), 135 r.getControls()); 136 } 137 else if (r instanceof LDIFModifyChangeRecord) 138 { 139 final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r; 140 final Modification[] originalMods = modRecord.getModifications(); 141 final Modification[] newMods = new Modification[originalMods.length]; 142 for (int i=0; i < originalMods.length; i++) 143 { 144 final Modification m = originalMods[i]; 145 if (m.hasValue()) 146 { 147 final String[] originalValues = m.getValues(); 148 final String[] newValues = new String[originalValues.length]; 149 for (int j=0; j < originalValues.length; j++) 150 { 151 newValues[j] = processString(originalValues[j]); 152 } 153 newMods[i] = new Modification(m.getModificationType(), 154 m.getAttributeName(), newValues); 155 } 156 else 157 { 158 newMods[i] = originalMods[i]; 159 } 160 } 161 162 return new LDIFModifyChangeRecord(processString(modRecord.getDN()), 163 newMods, modRecord.getControls()); 164 } 165 else if (r instanceof LDIFModifyDNChangeRecord) 166 { 167 final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r; 168 return new LDIFModifyDNChangeRecord(processString(modDNRecord.getDN()), 169 modDNRecord.getNewRDN(), modDNRecord.deleteOldRDN(), 170 processString(modDNRecord.getNewSuperiorDN()), 171 modDNRecord.getControls()); 172 } 173 else 174 { 175 // This should never happen. 176 return r; 177 } 178 } 179 180 181 182 /** 183 * Identifies whether the provided string represents a DN that is at or below 184 * the specified source base DN. If so, then it will be updated to replace 185 * the old base DN with the new base DN. Otherwise, the original string will 186 * be returned. 187 * 188 * @param s The string to process. 189 * 190 * @return A new string if the provided value was a valid DN at or below the 191 * source DN, or the original string if it was not a valid DN or was 192 * not below the source DN. 193 */ 194 String processString(final String s) 195 { 196 if (s == null) 197 { 198 return null; 199 } 200 201 try 202 { 203 final DN dn = new DN(s); 204 if (! dn.isDescendantOf(sourceDN, true)) 205 { 206 return s; 207 } 208 209 final RDN[] originalRDNs = dn.getRDNs(); 210 final RDN[] sourceRDNs = sourceDN.getRDNs(); 211 final ArrayList<RDN> newRDNs = new ArrayList<RDN>(2*originalRDNs.length); 212 final int numComponentsToKeep = originalRDNs.length - sourceRDNs.length; 213 for (int i=0; i < numComponentsToKeep; i++) 214 { 215 newRDNs.add(originalRDNs[i]); 216 } 217 218 newRDNs.addAll(targetRDNs); 219 return new DN(newRDNs).toString(); 220 } 221 catch (final Exception e) 222 { 223 // This is fine. The value isn't a DN. 224 return s; 225 } 226 } 227 228 229 230 /** 231 * {@inheritDoc} 232 */ 233 public Entry translate(final Entry original, final long firstLineNumber) 234 { 235 return transformEntry(original); 236 } 237 238 239 240 /** 241 * {@inheritDoc} 242 */ 243 public LDIFChangeRecord translate(final LDIFChangeRecord original, 244 final long firstLineNumber) 245 { 246 return transformChangeRecord(original); 247 } 248 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 public Entry translateEntryToWrite(final Entry original) 255 { 256 return transformEntry(original); 257 } 258 259 260 261 /** 262 * {@inheritDoc} 263 */ 264 public LDIFChangeRecord translateChangeRecordToWrite( 265 final LDIFChangeRecord original) 266 { 267 return transformChangeRecord(original); 268 } 269}