001/* 002 * Copyright 2016-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2016-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.transformations; 037 038 039 040import java.io.Serializable; 041import java.util.concurrent.atomic.AtomicLong; 042 043import com.unboundid.ldap.sdk.DN; 044import com.unboundid.ldap.sdk.Entry; 045import com.unboundid.ldap.sdk.Filter; 046import com.unboundid.ldap.sdk.SearchScope; 047import com.unboundid.ldap.sdk.schema.Schema; 048import com.unboundid.util.Debug; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053 054 055 056/** 057 * This class provides an implementation of an entry transformation that will 058 * return {@code null} for any entry that matches (or alternately, does not 059 * match) a given set of criteria and should therefore be excluded from the data 060 * set. 061 */ 062@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 063public final class ExcludeEntryTransformation 064 implements EntryTransformation, Serializable 065{ 066 /** 067 * The serial version UID for this serializable class. 068 */ 069 private static final long serialVersionUID = 103514669827637043L; 070 071 072 073 // An optional counter that will be incremented for each entry that has been 074 // excluded. 075 @Nullable private final AtomicLong excludedCount; 076 077 // Indicates whether we need to check entries against the filter. 078 private final boolean allEntriesMatchFilter; 079 080 // Indicates whether we need to check entries against the scope. 081 private final boolean allEntriesAreInScope; 082 083 // Indicates whether to exclude entries that match the criteria, or to exclude 084 // entries that do no not match the criteria. 085 private final boolean excludeMatching; 086 087 // The base DN to use to identify entries to exclude. 088 @NotNull private final DN baseDN; 089 090 // The filter to use to identify entries to exclude. 091 @NotNull private final Filter filter; 092 093 // The schema to use when processing. 094 @Nullable private final Schema schema; 095 096 // The scope to use to identify entries to exclude. 097 @NotNull private final SearchScope scope; 098 099 100 101 /** 102 * Creates a new exclude entry transformation with the provided information. 103 * 104 * @param schema The schema to use in processing. It may be 105 * {@code null} if a default standard schema should 106 * be used. 107 * @param baseDN The base DN to use to identify which entries to 108 * suppress. If this is {@code null}, it will be 109 * assumed to be the null DN. 110 * @param scope The scope to use to identify which entries to 111 * suppress. If this is {@code null}, it will be 112 * assumed to be {@link SearchScope#SUB}. 113 * @param filter An optional filter to use to identify which 114 * entries to suppress. If this is {@code null}, 115 * then a default LDAP true filter (which will match 116 * any entry) will be used. 117 * @param excludeMatching Indicates whether to exclude entries that match 118 * the criteria (if {@code true}) or to exclude 119 * entries that do not match the criteria (if 120 * {@code false}). 121 * @param excludedCount An optional counter that will be incremented for 122 * each entry that is excluded. 123 */ 124 public ExcludeEntryTransformation(@Nullable final Schema schema, 125 @Nullable final DN baseDN, 126 @Nullable final SearchScope scope, 127 @Nullable final Filter filter, 128 final boolean excludeMatching, 129 @Nullable final AtomicLong excludedCount) 130 { 131 this.excludeMatching = excludeMatching; 132 this.excludedCount = excludedCount; 133 134 135 // If a schema was provided, then use it. Otherwise, use the default 136 // standard schema. 137 Schema s = schema; 138 if (s == null) 139 { 140 try 141 { 142 s = Schema.getDefaultStandardSchema(); 143 } 144 catch (final Exception e) 145 { 146 // This should never happen. 147 Debug.debugException(e); 148 } 149 } 150 this.schema = s; 151 152 153 // If a base DN was provided, then use it. Otherwise, use the null DN. 154 if (baseDN == null) 155 { 156 this.baseDN = DN.NULL_DN; 157 } 158 else 159 { 160 this.baseDN = baseDN; 161 } 162 163 164 // If a scope was provided, then use it. Otherwise, use a subtree scope. 165 if (scope == null) 166 { 167 this.scope = SearchScope.SUB; 168 } 169 else 170 { 171 this.scope = scope; 172 } 173 allEntriesAreInScope = 174 (this.baseDN.isNullDN() && (this.scope == SearchScope.SUB)); 175 176 177 // If a filter was provided, then use it. Otherwise, use an LDAP true 178 // filter. 179 if (filter == null) 180 { 181 this.filter = Filter.createANDFilter(); 182 allEntriesMatchFilter = true; 183 } 184 else 185 { 186 this.filter = filter; 187 if (filter.getFilterType() == Filter.FILTER_TYPE_AND) 188 { 189 allEntriesMatchFilter = (filter.getComponents().length == 0); 190 } 191 else 192 { 193 allEntriesMatchFilter = false; 194 } 195 } 196 } 197 198 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override() 204 @Nullable() 205 public Entry transformEntry(@NotNull final Entry e) 206 { 207 if (e == null) 208 { 209 return null; 210 } 211 212 213 // Determine whether the entry is within the configured scope. 214 boolean matchesScope; 215 try 216 { 217 matchesScope = 218 (allEntriesAreInScope || e.matchesBaseAndScope(baseDN, scope)); 219 } 220 catch (final Exception ex) 221 { 222 Debug.debugException(ex); 223 224 // This should only happen if the entry has a malformed DN. In that 225 // case, we'll say that it doesn't match the scope. 226 matchesScope = false; 227 } 228 229 230 // Determine whether the entry matches the suppression filter. 231 boolean matchesFilter; 232 try 233 { 234 matchesFilter = (allEntriesMatchFilter || filter.matchesEntry(e, schema)); 235 } 236 catch (final Exception ex) 237 { 238 Debug.debugException(ex); 239 240 // This should only happen if the filter is one that we can't process at 241 // all or against the target entry. In that case, we'll say that it 242 // doesn't match the filter. 243 matchesFilter = false; 244 } 245 246 247 if (matchesScope && matchesFilter) 248 { 249 if (excludeMatching) 250 { 251 if (excludedCount != null) 252 { 253 excludedCount.incrementAndGet(); 254 } 255 return null; 256 } 257 else 258 { 259 return e; 260 } 261 } 262 else 263 { 264 if (excludeMatching) 265 { 266 return e; 267 } 268 else 269 { 270 if (excludedCount != null) 271 { 272 excludedCount.incrementAndGet(); 273 } 274 return null; 275 } 276 } 277 } 278 279 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override() 285 @Nullable() 286 public Entry translate(@NotNull final Entry original, 287 final long firstLineNumber) 288 { 289 return transformEntry(original); 290 } 291 292 293 294 /** 295 * {@inheritDoc} 296 */ 297 @Override() 298 @Nullable() 299 public Entry translateEntryToWrite(@NotNull final Entry original) 300 { 301 return transformEntry(original); 302 } 303}