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}