001/* 002 * Copyright 2016-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2016-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.transformations; 037 038 039 040import java.util.Collections; 041import java.util.HashSet; 042import java.util.Set; 043 044import com.unboundid.ldap.sdk.Attribute; 045import com.unboundid.ldap.sdk.DN; 046import com.unboundid.ldap.sdk.Entry; 047import com.unboundid.ldap.sdk.Filter; 048import com.unboundid.ldap.sdk.SearchScope; 049import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 050import com.unboundid.ldap.sdk.schema.Schema; 051import com.unboundid.util.Debug; 052import com.unboundid.util.StaticUtils; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055 056 057 058/** 059 * This class provides an implementation of an entry transformation that will 060 * add a specified attribute with a given set of values to any entry that does 061 * not already contain that attribute and matches a specified set of criteria. 062 */ 063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 064public final class AddAttributeTransformation 065 implements EntryTransformation 066{ 067 // The attribute to add if appropriate. 068 private final Attribute attributeToAdd; 069 070 // Indicates whether we need to check entries against the filter. 071 private final boolean examineFilter; 072 073 // Indicates whether we need to check entries against the scope. 074 private final boolean examineScope; 075 076 // Indicates whether to only add the attribute to entries that do not already 077 // have any values for the associated attribute type. 078 private final boolean onlyIfMissing; 079 080 // The base DN to use to identify entries to which to add the attribute. 081 private final DN baseDN; 082 083 // The filter to use to identify entries to which to add the attribute. 084 private final Filter filter; 085 086 // The schema to use when processing. 087 private final Schema schema; 088 089 // The scope to use to identify entries to which to add the attribute. 090 private final SearchScope scope; 091 092 // The names that can be used to reference the target attribute. 093 private final Set<String> names; 094 095 096 097 /** 098 * Creates a new add attribute transformation with the provided information. 099 * 100 * @param schema The schema to use in processing. It may be 101 * {@code null} if a default standard schema should be 102 * used. 103 * @param baseDN The base DN to use to identify which entries to 104 * update. If this is {@code null}, it will be 105 * assumed to be the null DN. 106 * @param scope The scope to use to identify which entries to 107 * update. If this is {@code null}, it will be 108 * assumed to be {@link SearchScope#SUB}. 109 * @param filter An optional filter to use to identify which entries 110 * to update. If this is {@code null}, then a default 111 * LDAP true filter (which will match any entry) will 112 * be used. 113 * @param attributeToAdd The attribute to add to entries that match the 114 * criteria and do not already contain any values for 115 * the specified attribute. It must not be 116 * {@code null}. 117 * @param onlyIfMissing Indicates whether the attribute should only be 118 * added to entries that do not already contain it. 119 * If this is {@code false} and an entry that matches 120 * the base, scope, and filter criteria and already 121 * has one or more values for the target attribute 122 * will be updated to include the new values in 123 * addition to the existing values. 124 */ 125 public AddAttributeTransformation(final Schema schema, final DN baseDN, 126 final SearchScope scope, 127 final Filter filter, 128 final Attribute attributeToAdd, 129 final boolean onlyIfMissing) 130 { 131 this.attributeToAdd = attributeToAdd; 132 this.onlyIfMissing = onlyIfMissing; 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 // Identify all of the names that can be used to reference the specified 154 // attribute. 155 final HashSet<String> attrNames = 156 new HashSet<>(StaticUtils.computeMapCapacity(5)); 157 final String baseName = 158 StaticUtils.toLowerCase(attributeToAdd.getBaseName()); 159 attrNames.add(baseName); 160 if (s != null) 161 { 162 final AttributeTypeDefinition at = s.getAttributeType(baseName); 163 if (at != null) 164 { 165 attrNames.add(StaticUtils.toLowerCase(at.getOID())); 166 for (final String name : at.getNames()) 167 { 168 attrNames.add(StaticUtils.toLowerCase(name)); 169 } 170 } 171 } 172 names = Collections.unmodifiableSet(attrNames); 173 174 175 // If a base DN was provided, then use it. Otherwise, use the null DN. 176 if (baseDN == null) 177 { 178 this.baseDN = DN.NULL_DN; 179 } 180 else 181 { 182 this.baseDN = baseDN; 183 } 184 185 186 // If a scope was provided, then use it. Otherwise, use a subtree scope. 187 if (scope == null) 188 { 189 this.scope = SearchScope.SUB; 190 } 191 else 192 { 193 this.scope = scope; 194 } 195 196 197 // If a filter was provided, then use it. Otherwise, use an LDAP true 198 // filter. 199 if (filter == null) 200 { 201 this.filter = Filter.createANDFilter(); 202 examineFilter = false; 203 } 204 else 205 { 206 this.filter = filter; 207 if (filter.getFilterType() == Filter.FILTER_TYPE_AND) 208 { 209 examineFilter = (filter.getComponents().length > 0); 210 } 211 else 212 { 213 examineFilter = true; 214 } 215 } 216 217 218 examineScope = 219 (! (this.baseDN.isNullDN() && this.scope == SearchScope.SUB)); 220 } 221 222 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override() 228 public Entry transformEntry(final Entry e) 229 { 230 if (e == null) 231 { 232 return null; 233 } 234 235 236 // If we should only add the attribute to entries that don't already contain 237 // any values for that type, then determine whether the target attribute 238 // already exists in the entry. If so, then just return the original entry. 239 if (onlyIfMissing) 240 { 241 for (final String name : names) 242 { 243 if (e.hasAttribute(name)) 244 { 245 return e; 246 } 247 } 248 } 249 250 251 // Determine whether the entry is within the scope of the inclusion 252 // criteria. If not, then return the original entry. 253 try 254 { 255 if (examineScope && (! e.matchesBaseAndScope(baseDN, scope))) 256 { 257 return e; 258 } 259 } 260 catch (final Exception ex) 261 { 262 // This should only happen if the entry has a malformed DN. In that case, 263 // we'll assume it isn't within the scope and return the provided entry. 264 Debug.debugException(ex); 265 return e; 266 } 267 268 269 // Determine whether the entry matches the suppression filter. If not, then 270 // return the original entry. 271 try 272 { 273 if (examineFilter && (! filter.matchesEntry(e, schema))) 274 { 275 return e; 276 } 277 } 278 catch (final Exception ex) 279 { 280 // If we can't verify whether the entry matches the filter, then assume 281 // it doesn't and return the provided entry. 282 Debug.debugException(ex); 283 return e; 284 } 285 286 287 // If we've gotten here, then we should add the attribute to the entry. 288 final Entry copy = e.duplicate(); 289 final Attribute existingAttribute = 290 copy.getAttribute(attributeToAdd.getName(), schema); 291 if (existingAttribute == null) 292 { 293 copy.addAttribute(attributeToAdd); 294 } 295 else 296 { 297 copy.addAttribute(existingAttribute.getName(), 298 attributeToAdd.getValueByteArrays()); 299 } 300 return copy; 301 } 302 303 304 305 /** 306 * {@inheritDoc} 307 */ 308 @Override() 309 public Entry translate(final Entry original, final long firstLineNumber) 310 { 311 return transformEntry(original); 312 } 313 314 315 316 /** 317 * {@inheritDoc} 318 */ 319 @Override() 320 public Entry translateEntryToWrite(final Entry original) 321 { 322 return transformEntry(original); 323 } 324}