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.io.Serializable;
026    import java.util.concurrent.atomic.AtomicLong;
027    
028    import com.unboundid.ldap.sdk.DN;
029    import com.unboundid.ldap.sdk.Entry;
030    import com.unboundid.ldap.sdk.Filter;
031    import com.unboundid.ldap.sdk.SearchScope;
032    import com.unboundid.ldap.sdk.schema.Schema;
033    import com.unboundid.util.Debug;
034    import com.unboundid.util.ThreadSafety;
035    import 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)
046    public 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    }