001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 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 = new HashSet<String>(5); 155 final String baseName = 156 StaticUtils.toLowerCase(Attribute.getBaseName(attributeName)); 157 nameSet.add(baseName); 158 if (s != null) 159 { 160 final AttributeTypeDefinition at = s.getAttributeType(baseName); 161 if (at != null) 162 { 163 nameSet.add(StaticUtils.toLowerCase(at.getOID())); 164 for (final String name : at.getNames()) 165 { 166 nameSet.add(StaticUtils.toLowerCase(name)); 167 } 168 } 169 } 170 names = Collections.unmodifiableSet(nameSet); 171 } 172 173 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override() 179 public Entry transformEntry(final Entry e) 180 { 181 if (e == null) 182 { 183 return null; 184 } 185 186 187 // See if the DN contains the target attribute in the RDN. If so, then 188 // replace its value. 189 String dn = e.getDN(); 190 String newValue = null; 191 if (replaceInRDN) 192 { 193 try 194 { 195 final DN parsedDN = new DN(dn); 196 final RDN rdn = parsedDN.getRDN(); 197 for (final String name : names) 198 { 199 if (rdn.hasAttribute(name)) 200 { 201 newValue = 202 beforeText + counter.getAndAdd(incrementAmount) + afterText; 203 break; 204 } 205 } 206 207 if (newValue != null) 208 { 209 if (rdn.isMultiValued()) 210 { 211 final String[] attrNames = rdn.getAttributeNames(); 212 final byte[][] originalValues = rdn.getByteArrayAttributeValues(); 213 final byte[][] newValues = new byte[originalValues.length][]; 214 for (int i=0; i < attrNames.length; i++) 215 { 216 if (names.contains(StaticUtils.toLowerCase(attrNames[i]))) 217 { 218 newValues[i] = StaticUtils.getBytes(newValue); 219 } 220 else 221 { 222 newValues[i] = originalValues[i]; 223 } 224 } 225 dn = new DN(new RDN(attrNames, newValues, schema), 226 parsedDN.getParent()).toString(); 227 } 228 else 229 { 230 dn = new DN(new RDN(rdn.getAttributeNames()[0], newValue, schema), 231 parsedDN.getParent()).toString(); 232 } 233 } 234 } 235 catch (final Exception ex) 236 { 237 Debug.debugException(ex); 238 } 239 } 240 241 242 // If the RDN doesn't contain the target attribute, then see if the entry 243 // contains the target attribute. If not, then just return the provided 244 // entry. 245 if (newValue == null) 246 { 247 boolean hasAttribute = false; 248 for (final String name : names) 249 { 250 if (e.hasAttribute(name)) 251 { 252 hasAttribute = true; 253 break; 254 } 255 } 256 257 if (! hasAttribute) 258 { 259 return e; 260 } 261 } 262 263 264 // If we haven't computed the new value for this entry, then do so now. 265 if (newValue == null) 266 { 267 newValue = beforeText + counter.getAndAdd(incrementAmount) + afterText; 268 } 269 270 271 // Iterate through the attributes in the entry and make the appropriate 272 // updates. 273 final Collection<Attribute> originalAttributes = e.getAttributes(); 274 final ArrayList<Attribute> updatedAttributes = 275 new ArrayList<Attribute>(originalAttributes.size()); 276 for (final Attribute a : originalAttributes) 277 { 278 if (names.contains(StaticUtils.toLowerCase(a.getBaseName()))) 279 { 280 updatedAttributes.add(new Attribute(a.getName(), schema, newValue)); 281 } 282 else 283 { 284 updatedAttributes.add(a); 285 } 286 } 287 288 289 // Return the updated entry. 290 return new Entry(dn, schema, updatedAttributes); 291 } 292 293 294 295 /** 296 * {@inheritDoc} 297 */ 298 @Override() 299 public Entry translate(final Entry original, final long firstLineNumber) 300 { 301 return transformEntry(original); 302 } 303 304 305 306 /** 307 * {@inheritDoc} 308 */ 309 @Override() 310 public Entry translateEntryToWrite(final Entry original) 311 { 312 return transformEntry(original); 313 } 314}