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