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.Collections; 026import java.util.HashSet; 027import java.util.Set; 028 029import com.unboundid.ldap.sdk.Attribute; 030import com.unboundid.ldap.sdk.DN; 031import com.unboundid.ldap.sdk.Entry; 032import com.unboundid.ldap.sdk.Filter; 033import com.unboundid.ldap.sdk.SearchScope; 034import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 035import com.unboundid.ldap.sdk.schema.Schema; 036import com.unboundid.util.Debug; 037import com.unboundid.util.StaticUtils; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040 041 042 043/** 044 * This class provides an implementation of an entry transformation that will 045 * add a specified attribute with a given set of values to any entry that does 046 * not already contain that attribute and matches a specified set of criteria. 047 */ 048@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 049public final class AddAttributeTransformation 050 implements EntryTransformation 051{ 052 // The attribute to add if appropriate. 053 private final Attribute attributeToAdd; 054 055 // Indicates whether we need to check entries against the filter. 056 private final boolean examineFilter; 057 058 // Indicates whether we need to check entries against the scope. 059 private final boolean examineScope; 060 061 // Indicates whether to only add the attribute to entries that do not already 062 // have any values for the associated attribute type. 063 private final boolean onlyIfMissing; 064 065 // The base DN to use to identify entries to which to add the attribute. 066 private final DN baseDN; 067 068 // The filter to use to identify entries to which to add the attribute. 069 private final Filter filter; 070 071 // The schema to use when processing. 072 private final Schema schema; 073 074 // The scope to use to identify entries to which to add the attribute. 075 private final SearchScope scope; 076 077 // The names that can be used to reference the target attribute. 078 private final Set<String> names; 079 080 081 082 /** 083 * Creates a new add attribute transformation with the provided information. 084 * 085 * @param schema The schema to use in processing. It may be 086 * {@code null} if a default standard schema should be 087 * used. 088 * @param baseDN The base DN to use to identify which entries to 089 * update. If this is {@code null}, it will be 090 * assumed to be the null DN. 091 * @param scope The scope to use to identify which entries to 092 * update. If this is {@code null}, it will be 093 * assumed to be {@link SearchScope#SUB}. 094 * @param filter An optional filter to use to identify which entries 095 * to update. If this is {@code null}, then a default 096 * LDAP true filter (which will match any entry) will 097 * be used. 098 * @param attributeToAdd The attribute to add to entries that match the 099 * criteria and do not already contain any values for 100 * the specified attribute. It must not be 101 * {@code null}. 102 * @param onlyIfMissing Indicates whether the attribute should only be 103 * added to entries that do not already contain it. 104 * If this is {@code false} and an entry that matches 105 * the base, scope, and filter criteria and already 106 * has one or more values for the target attribute 107 * will be updated to include the new values in 108 * addition to the existing values. 109 */ 110 public AddAttributeTransformation(final Schema schema, final DN baseDN, 111 final SearchScope scope, 112 final Filter filter, 113 final Attribute attributeToAdd, 114 final boolean onlyIfMissing) 115 { 116 this.attributeToAdd = attributeToAdd; 117 this.onlyIfMissing = onlyIfMissing; 118 119 120 // If a schema was provided, then use it. Otherwise, use the default 121 // standard schema. 122 Schema s = schema; 123 if (s == null) 124 { 125 try 126 { 127 s = Schema.getDefaultStandardSchema(); 128 } 129 catch (final Exception e) 130 { 131 // This should never happen. 132 Debug.debugException(e); 133 } 134 } 135 this.schema = s; 136 137 138 // Identify all of the names that can be used to reference the specified 139 // attribute. 140 final HashSet<String> attrNames = new HashSet<String>(5); 141 final String baseName = 142 StaticUtils.toLowerCase(attributeToAdd.getBaseName()); 143 attrNames.add(baseName); 144 if (s != null) 145 { 146 final AttributeTypeDefinition at = s.getAttributeType(baseName); 147 if (at != null) 148 { 149 attrNames.add(StaticUtils.toLowerCase(at.getOID())); 150 for (final String name : at.getNames()) 151 { 152 attrNames.add(StaticUtils.toLowerCase(name)); 153 } 154 } 155 } 156 names = Collections.unmodifiableSet(attrNames); 157 158 159 // If a base DN was provided, then use it. Otherwise, use the null DN. 160 if (baseDN == null) 161 { 162 this.baseDN = DN.NULL_DN; 163 } 164 else 165 { 166 this.baseDN = baseDN; 167 } 168 169 170 // If a scope was provided, then use it. Otherwise, use a subtree scope. 171 if (scope == null) 172 { 173 this.scope = SearchScope.SUB; 174 } 175 else 176 { 177 this.scope = scope; 178 } 179 180 181 // If a filter was provided, then use it. Otherwise, use an LDAP true 182 // filter. 183 if (filter == null) 184 { 185 this.filter = Filter.createANDFilter(); 186 examineFilter = false; 187 } 188 else 189 { 190 this.filter = filter; 191 if (filter.getFilterType() == Filter.FILTER_TYPE_AND) 192 { 193 examineFilter = (filter.getComponents().length > 0); 194 } 195 else 196 { 197 examineFilter = true; 198 } 199 } 200 201 202 examineScope = 203 (! (this.baseDN.isNullDN() && this.scope == SearchScope.SUB)); 204 } 205 206 207 208 /** 209 * {@inheritDoc} 210 */ 211 public Entry transformEntry(final Entry e) 212 { 213 if (e == null) 214 { 215 return null; 216 } 217 218 219 // If we should only add the attribute to entries that don't already contain 220 // any values for that type, then determine whether the target attribute 221 // already exists in the entry. If so, then just return the original entry. 222 if (onlyIfMissing) 223 { 224 for (final String name : names) 225 { 226 if (e.hasAttribute(name)) 227 { 228 return e; 229 } 230 } 231 } 232 233 234 // Determine whether the entry is within the scope of the inclusion 235 // criteria. If not, then return the original entry. 236 try 237 { 238 if (examineScope && (! e.matchesBaseAndScope(baseDN, scope))) 239 { 240 return e; 241 } 242 } 243 catch (final Exception ex) 244 { 245 // This should only happen if the entry has a malformed DN. In that case, 246 // we'll assume it isn't within the scope and return the provided entry. 247 Debug.debugException(ex); 248 return e; 249 } 250 251 252 // Determine whether the entry matches the suppression filter. If not, then 253 // return the original entry. 254 try 255 { 256 if (examineFilter && (! filter.matchesEntry(e, schema))) 257 { 258 return e; 259 } 260 } 261 catch (final Exception ex) 262 { 263 // If we can't verify whether the entry matches the filter, then assume 264 // it doesn't and return the provided entry. 265 Debug.debugException(ex); 266 return e; 267 } 268 269 270 // If we've gotten here, then we should add the attribute to the entry. 271 final Entry copy = e.duplicate(); 272 final Attribute existingAttribute = 273 copy.getAttribute(attributeToAdd.getName(), schema); 274 if (existingAttribute == null) 275 { 276 copy.addAttribute(attributeToAdd); 277 } 278 else 279 { 280 copy.addAttribute(existingAttribute.getName(), 281 attributeToAdd.getValueByteArrays()); 282 } 283 return copy; 284 } 285 286 287 288 /** 289 * {@inheritDoc} 290 */ 291 public Entry translate(final Entry original, final long firstLineNumber) 292 { 293 return transformEntry(original); 294 } 295 296 297 298 /** 299 * {@inheritDoc} 300 */ 301 public Entry translateEntryToWrite(final Entry original) 302 { 303 return transformEntry(original); 304 } 305}