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 }