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.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.Set; 030import java.util.concurrent.atomic.AtomicLong; 031 032import com.unboundid.ldap.sdk.Attribute; 033import com.unboundid.ldap.sdk.DN; 034import com.unboundid.ldap.sdk.Entry; 035import com.unboundid.ldap.sdk.RDN; 036import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 037import com.unboundid.ldap.sdk.schema.Schema; 038import com.unboundid.util.Debug; 039import com.unboundid.util.StaticUtils; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043 044 045/** 046 * This class provides an implementation of an entry transformation that will 047 * replace the existing set of values for a given attribute with a value that 048 * contains a numeric counter (optionally along with additional static text) 049 * that increments for each entry that contains the target attribute. The 050 * resulting attribute will only have a single value, even if it originally had 051 * multiple values. 052 */ 053@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 054public final class ReplaceWithCounterTransformation 055 implements EntryTransformation 056{ 057 // The counter to use to obtain the values. 058 private final AtomicLong counter; 059 060 // Indicates whether to update the DN of the target entry if its RDN includes 061 // the target attribute. 062 private final boolean replaceInRDN; 063 064 // The amount by which to increment the counter for each entry. 065 private final long incrementAmount; 066 067 // The schema to use when processing. 068 private final Schema schema; 069 070 // The names that may be used to reference the attribute to replace. 071 private final Set<String> names; 072 073 // The static text that will appear after the number in generated values. 074 private final String afterText; 075 076 // The static text that will appear before the number in generated values. 077 private final String beforeText; 078 079 080 081 /** 082 * Creates a new replace with counter transformation using the provided 083 * information. 084 * 085 * @param schema The schema to use to identify alternate names for 086 * the target attribute. This may be {@code null} if 087 * a default standard schema should be used. 088 * @param attributeName The name of the attribute that should be replaced 089 * with the generated value. 090 * @param initialValue The initial value to use for the counter. 091 * @param incrementAmount The amount by which the counter should be 092 * incremented for each entry containing the target 093 * attribute. 094 * @param beforeText An optional string that should appear before the 095 * counter in generated values. It may be 096 * {@code null} if no before text should be used. 097 * @param afterText An optional string that should appear after the 098 * counter in generated values. It may be 099 * {@code null} if no after text should be used. 100 * @param replaceInRDN Indicates whether to update the DN of the target 101 * entry if its RDN includes the target attribute. 102 */ 103 public ReplaceWithCounterTransformation(final Schema schema, 104 final String attributeName, 105 final long initialValue, 106 final long incrementAmount, 107 final String beforeText, 108 final String afterText, 109 final boolean replaceInRDN) 110 { 111 this.incrementAmount = incrementAmount; 112 this.replaceInRDN = replaceInRDN; 113 114 counter = new AtomicLong(initialValue); 115 116 if (beforeText == null) 117 { 118 this.beforeText = ""; 119 } 120 else 121 { 122 this.beforeText = beforeText; 123 } 124 125 if (afterText == null) 126 { 127 this.afterText = ""; 128 } 129 else 130 { 131 this.afterText = afterText; 132 } 133 134 135 // If a schema was provided, then use it. Otherwise, use the default 136 // standard schema. 137 Schema s = schema; 138 if (s == null) 139 { 140 try 141 { 142 s = Schema.getDefaultStandardSchema(); 143 } 144 catch (final Exception e) 145 { 146 // This should never happen. 147 Debug.debugException(e); 148 } 149 } 150 this.schema = s; 151 152 153 // Get all names that can be used to reference the target attribute. 154 final HashSet<String> nameSet = 155 new HashSet<>(StaticUtils.computeMapCapacity(5)); 156 final String baseName = 157 StaticUtils.toLowerCase(Attribute.getBaseName(attributeName)); 158 nameSet.add(baseName); 159 if (s != null) 160 { 161 final AttributeTypeDefinition at = s.getAttributeType(baseName); 162 if (at != null) 163 { 164 nameSet.add(StaticUtils.toLowerCase(at.getOID())); 165 for (final String name : at.getNames()) 166 { 167 nameSet.add(StaticUtils.toLowerCase(name)); 168 } 169 } 170 } 171 names = Collections.unmodifiableSet(nameSet); 172 } 173 174 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override() 180 public Entry transformEntry(final Entry e) 181 { 182 if (e == null) 183 { 184 return null; 185 } 186 187 188 // See if the DN contains the target attribute in the RDN. If so, then 189 // replace its value. 190 String dn = e.getDN(); 191 String newValue = null; 192 if (replaceInRDN) 193 { 194 try 195 { 196 final DN parsedDN = new DN(dn); 197 final RDN rdn = parsedDN.getRDN(); 198 for (final String name : names) 199 { 200 if (rdn.hasAttribute(name)) 201 { 202 newValue = 203 beforeText + counter.getAndAdd(incrementAmount) + afterText; 204 break; 205 } 206 } 207 208 if (newValue != null) 209 { 210 if (rdn.isMultiValued()) 211 { 212 final String[] attrNames = rdn.getAttributeNames(); 213 final byte[][] originalValues = rdn.getByteArrayAttributeValues(); 214 final byte[][] newValues = new byte[originalValues.length][]; 215 for (int i=0; i < attrNames.length; i++) 216 { 217 if (names.contains(StaticUtils.toLowerCase(attrNames[i]))) 218 { 219 newValues[i] = StaticUtils.getBytes(newValue); 220 } 221 else 222 { 223 newValues[i] = originalValues[i]; 224 } 225 } 226 dn = new DN(new RDN(attrNames, newValues, schema), 227 parsedDN.getParent()).toString(); 228 } 229 else 230 { 231 dn = new DN(new RDN(rdn.getAttributeNames()[0], newValue, schema), 232 parsedDN.getParent()).toString(); 233 } 234 } 235 } 236 catch (final Exception ex) 237 { 238 Debug.debugException(ex); 239 } 240 } 241 242 243 // If the RDN doesn't contain the target attribute, then see if the entry 244 // contains the target attribute. If not, then just return the provided 245 // entry. 246 if (newValue == null) 247 { 248 boolean hasAttribute = false; 249 for (final String name : names) 250 { 251 if (e.hasAttribute(name)) 252 { 253 hasAttribute = true; 254 break; 255 } 256 } 257 258 if (! hasAttribute) 259 { 260 return e; 261 } 262 } 263 264 265 // If we haven't computed the new value for this entry, then do so now. 266 if (newValue == null) 267 { 268 newValue = beforeText + counter.getAndAdd(incrementAmount) + afterText; 269 } 270 271 272 // Iterate through the attributes in the entry and make the appropriate 273 // updates. 274 final Collection<Attribute> originalAttributes = e.getAttributes(); 275 final ArrayList<Attribute> updatedAttributes = 276 new ArrayList<>(originalAttributes.size()); 277 for (final Attribute a : originalAttributes) 278 { 279 if (names.contains(StaticUtils.toLowerCase(a.getBaseName()))) 280 { 281 updatedAttributes.add(new Attribute(a.getName(), schema, newValue)); 282 } 283 else 284 { 285 updatedAttributes.add(a); 286 } 287 } 288 289 290 // Return the updated entry. 291 return new Entry(dn, schema, updatedAttributes); 292 } 293 294 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override() 300 public Entry translate(final Entry original, final long firstLineNumber) 301 { 302 return transformEntry(original); 303 } 304 305 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override() 311 public Entry translateEntryToWrite(final Entry original) 312 { 313 return transformEntry(original); 314 } 315}