001 /*
002 * Copyright 2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2016 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 */
021 package com.unboundid.ldap.sdk.transformations;
022
023
024
025 import java.util.Collections;
026 import java.util.HashSet;
027 import java.util.Set;
028
029 import com.unboundid.ldap.sdk.Attribute;
030 import com.unboundid.ldap.sdk.DN;
031 import com.unboundid.ldap.sdk.Entry;
032 import com.unboundid.ldap.sdk.Filter;
033 import com.unboundid.ldap.sdk.SearchScope;
034 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
035 import com.unboundid.ldap.sdk.schema.Schema;
036 import com.unboundid.util.Debug;
037 import com.unboundid.util.StaticUtils;
038 import com.unboundid.util.ThreadSafety;
039 import 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)
049 public 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 }