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}