001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 Ping Identity Corporation 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 @Override() 186 public Entry transformEntry(final Entry e) 187 { 188 if (e == null) 189 { 190 return null; 191 } 192 193 194 // Determine whether the entry is within the configured scope. 195 boolean matchesScope; 196 try 197 { 198 matchesScope = 199 (allEntriesAreInScope || e.matchesBaseAndScope(baseDN, scope)); 200 } 201 catch (final Exception ex) 202 { 203 Debug.debugException(ex); 204 205 // This should only happen if the entry has a malformed DN. In that 206 // case, we'll say that it doesn't match the scope. 207 matchesScope = false; 208 } 209 210 211 // Determine whether the entry matches the suppression filter. 212 boolean matchesFilter; 213 try 214 { 215 matchesFilter = (allEntriesMatchFilter || filter.matchesEntry(e, schema)); 216 } 217 catch (final Exception ex) 218 { 219 Debug.debugException(ex); 220 221 // This should only happen if the filter is one that we can't process at 222 // all or against the target entry. In that case, we'll say that it 223 // doesn't match the filter. 224 matchesFilter = false; 225 } 226 227 228 if (matchesScope && matchesFilter) 229 { 230 if (excludeMatching) 231 { 232 if (excludedCount != null) 233 { 234 excludedCount.incrementAndGet(); 235 } 236 return null; 237 } 238 else 239 { 240 return e; 241 } 242 } 243 else 244 { 245 if (excludeMatching) 246 { 247 return e; 248 } 249 else 250 { 251 if (excludedCount != null) 252 { 253 excludedCount.incrementAndGet(); 254 } 255 return null; 256 } 257 } 258 } 259 260 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override() 266 public Entry translate(final Entry original, final long firstLineNumber) 267 { 268 return transformEntry(original); 269 } 270 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override() 277 public Entry translateEntryToWrite(final Entry original) 278 { 279 return transformEntry(original); 280 } 281}