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