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