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.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<>(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 @Override() 212 public Entry transformEntry(final Entry e) 213 { 214 if (e == null) 215 { 216 return null; 217 } 218 219 220 // If we should only add the attribute to entries that don't already contain 221 // any values for that type, then determine whether the target attribute 222 // already exists in the entry. If so, then just return the original entry. 223 if (onlyIfMissing) 224 { 225 for (final String name : names) 226 { 227 if (e.hasAttribute(name)) 228 { 229 return e; 230 } 231 } 232 } 233 234 235 // Determine whether the entry is within the scope of the inclusion 236 // criteria. If not, then return the original entry. 237 try 238 { 239 if (examineScope && (! e.matchesBaseAndScope(baseDN, scope))) 240 { 241 return e; 242 } 243 } 244 catch (final Exception ex) 245 { 246 // This should only happen if the entry has a malformed DN. In that case, 247 // we'll assume it isn't within the scope and return the provided entry. 248 Debug.debugException(ex); 249 return e; 250 } 251 252 253 // Determine whether the entry matches the suppression filter. If not, then 254 // return the original entry. 255 try 256 { 257 if (examineFilter && (! filter.matchesEntry(e, schema))) 258 { 259 return e; 260 } 261 } 262 catch (final Exception ex) 263 { 264 // If we can't verify whether the entry matches the filter, then assume 265 // it doesn't and return the provided entry. 266 Debug.debugException(ex); 267 return e; 268 } 269 270 271 // If we've gotten here, then we should add the attribute to the entry. 272 final Entry copy = e.duplicate(); 273 final Attribute existingAttribute = 274 copy.getAttribute(attributeToAdd.getName(), schema); 275 if (existingAttribute == null) 276 { 277 copy.addAttribute(attributeToAdd); 278 } 279 else 280 { 281 copy.addAttribute(existingAttribute.getName(), 282 attributeToAdd.getValueByteArrays()); 283 } 284 return copy; 285 } 286 287 288 289 /** 290 * {@inheritDoc} 291 */ 292 @Override() 293 public Entry translate(final Entry original, final long firstLineNumber) 294 { 295 return transformEntry(original); 296 } 297 298 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override() 304 public Entry translateEntryToWrite(final Entry original) 305 { 306 return transformEntry(original); 307 } 308}