001/*
002 * Copyright 2016-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-2024 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-2024 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.io.Serializable;
041import java.util.concurrent.atomic.AtomicLong;
042
043import com.unboundid.ldap.sdk.DN;
044import com.unboundid.ldap.sdk.Entry;
045import com.unboundid.ldap.sdk.Filter;
046import com.unboundid.ldap.sdk.SearchScope;
047import com.unboundid.ldap.sdk.schema.Schema;
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotNull;
050import com.unboundid.util.Nullable;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053
054
055
056/**
057 * This class provides an implementation of an entry transformation that will
058 * return {@code null} for any entry that matches (or alternately, does not
059 * match) a given set of criteria and should therefore be excluded from the data
060 * set.
061 */
062@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
063public final class ExcludeEntryTransformation
064       implements EntryTransformation, Serializable
065{
066  /**
067   * The serial version UID for this serializable class.
068   */
069  private static final long serialVersionUID = 103514669827637043L;
070
071
072
073  // An optional counter that will be incremented for each entry that has been
074  // excluded.
075  @Nullable private final AtomicLong excludedCount;
076
077  // Indicates whether we need to check entries against the filter.
078  private final boolean allEntriesMatchFilter;
079
080  // Indicates whether we need to check entries against the scope.
081  private final boolean allEntriesAreInScope;
082
083  // Indicates whether to exclude entries that match the criteria, or to exclude
084  // entries that do no not match the criteria.
085  private final boolean excludeMatching;
086
087  // The base DN to use to identify entries to exclude.
088  @NotNull private final DN baseDN;
089
090  // The filter to use to identify entries to exclude.
091  @NotNull private final Filter filter;
092
093  // The schema to use when processing.
094  @Nullable private final Schema schema;
095
096  // The scope to use to identify entries to exclude.
097  @NotNull private final SearchScope scope;
098
099
100
101  /**
102   * Creates a new exclude entry transformation with the provided information.
103   *
104   * @param  schema           The schema to use in processing.  It may be
105   *                          {@code null} if a default standard schema should
106   *                          be used.
107   * @param  baseDN           The base DN to use to identify which entries to
108   *                          suppress.  If this is {@code null}, it will be
109   *                          assumed to be the null DN.
110   * @param  scope            The scope to use to identify which entries to
111   *                          suppress.  If this is {@code null}, it will be
112   *                          assumed to be {@link SearchScope#SUB}.
113   * @param  filter           An optional filter to use to identify which
114   *                          entries to suppress.  If this is {@code null},
115   *                          then a default LDAP true filter (which will match
116   *                          any entry) will be used.
117   * @param  excludeMatching  Indicates whether to exclude entries that match
118   *                          the criteria (if {@code true}) or to exclude
119   *                          entries that do not match the criteria (if
120   *                          {@code false}).
121   * @param  excludedCount    An optional counter that will be incremented for
122   *                          each entry that is excluded.
123   */
124  public ExcludeEntryTransformation(@Nullable final Schema schema,
125                                    @Nullable final DN baseDN,
126                                    @Nullable final SearchScope scope,
127                                    @Nullable final Filter filter,
128                                    final boolean excludeMatching,
129                                    @Nullable final AtomicLong excludedCount)
130  {
131    this.excludeMatching = excludeMatching;
132    this.excludedCount = excludedCount;
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    // If a base DN was provided, then use it.  Otherwise, use the null DN.
154    if (baseDN == null)
155    {
156      this.baseDN = DN.NULL_DN;
157    }
158    else
159    {
160      this.baseDN = baseDN;
161    }
162
163
164    // If a scope was provided, then use it.  Otherwise, use a subtree scope.
165    if (scope == null)
166    {
167      this.scope = SearchScope.SUB;
168    }
169    else
170    {
171      this.scope = scope;
172    }
173    allEntriesAreInScope =
174         (this.baseDN.isNullDN() && (this.scope == SearchScope.SUB));
175
176
177    // If a filter was provided, then use it.  Otherwise, use an LDAP true
178    // filter.
179    if (filter == null)
180    {
181      this.filter = Filter.createANDFilter();
182      allEntriesMatchFilter = true;
183    }
184    else
185    {
186      this.filter = filter;
187      if (filter.getFilterType() == Filter.FILTER_TYPE_AND)
188      {
189        allEntriesMatchFilter = (filter.getComponents().length == 0);
190      }
191      else
192      {
193        allEntriesMatchFilter = false;
194      }
195    }
196  }
197
198
199
200  /**
201   * {@inheritDoc}
202   */
203  @Override()
204  @Nullable()
205  public Entry transformEntry(@NotNull final Entry e)
206  {
207    if (e == null)
208    {
209      return null;
210    }
211
212
213    // Determine whether the entry is within the configured scope.
214    boolean matchesScope;
215    try
216    {
217      matchesScope =
218           (allEntriesAreInScope || e.matchesBaseAndScope(baseDN, scope));
219    }
220    catch (final Exception ex)
221    {
222      Debug.debugException(ex);
223
224      // This should only happen if the entry has a malformed DN.  In that
225      // case, we'll say that it doesn't match the scope.
226      matchesScope = false;
227    }
228
229
230    // Determine whether the entry matches the suppression filter.
231    boolean matchesFilter;
232    try
233    {
234      matchesFilter = (allEntriesMatchFilter || filter.matchesEntry(e, schema));
235    }
236    catch (final Exception ex)
237    {
238      Debug.debugException(ex);
239
240      // This should only happen if the filter is one that we can't process at
241      // all or against the target entry.  In that case, we'll say that it
242      // doesn't match the filter.
243      matchesFilter = false;
244    }
245
246
247    if (matchesScope && matchesFilter)
248    {
249      if (excludeMatching)
250      {
251        if (excludedCount != null)
252        {
253          excludedCount.incrementAndGet();
254        }
255        return null;
256      }
257      else
258      {
259        return e;
260      }
261    }
262    else
263    {
264      if (excludeMatching)
265      {
266        return e;
267      }
268      else
269      {
270        if (excludedCount != null)
271        {
272          excludedCount.incrementAndGet();
273        }
274        return null;
275      }
276    }
277  }
278
279
280
281  /**
282   * {@inheritDoc}
283   */
284  @Override()
285  @Nullable()
286  public Entry translate(@NotNull final Entry original,
287                         final long firstLineNumber)
288  {
289    return transformEntry(original);
290  }
291
292
293
294  /**
295   * {@inheritDoc}
296   */
297  @Override()
298  @Nullable()
299  public Entry translateEntryToWrite(@NotNull final Entry original)
300  {
301    return transformEntry(original);
302  }
303}