001/* 002 * Copyright 2016-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2020 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-2020 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.ThreadSafety; 050import com.unboundid.util.ThreadSafetyLevel; 051 052 053 054/** 055 * This class provides an implementation of an entry transformation that will 056 * return {@code null} for any entry that matches (or alternately, does not 057 * match) a given set of criteria and should therefore be excluded from the data 058 * set. 059 */ 060@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 061public final class ExcludeEntryTransformation 062 implements EntryTransformation, Serializable 063{ 064 /** 065 * The serial version UID for this serializable class. 066 */ 067 private static final long serialVersionUID = 103514669827637043L; 068 069 070 071 // An optional counter that will be incremented for each entry that has been 072 // excluded. 073 private final AtomicLong excludedCount; 074 075 // Indicates whether we need to check entries against the filter. 076 private final boolean allEntriesMatchFilter; 077 078 // Indicates whether we need to check entries against the scope. 079 private final boolean allEntriesAreInScope; 080 081 // Indicates whether to exclude entries that match the criteria, or to exclude 082 // entries that do no not match the criteria. 083 private final boolean excludeMatching; 084 085 // The base DN to use to identify entries to exclude. 086 private final DN baseDN; 087 088 // The filter to use to identify entries to exclude. 089 private final Filter filter; 090 091 // The schema to use when processing. 092 private final Schema schema; 093 094 // The scope to use to identify entries to exclude. 095 private final SearchScope scope; 096 097 098 099 /** 100 * Creates a new exclude entry transformation with the provided information. 101 * 102 * @param schema The schema to use in processing. It may be 103 * {@code null} if a default standard schema should 104 * be used. 105 * @param baseDN The base DN to use to identify which entries to 106 * suppress. If this is {@code null}, it will be 107 * assumed to be the null DN. 108 * @param scope The scope to use to identify which entries to 109 * suppress. If this is {@code null}, it will be 110 * assumed to be {@link SearchScope#SUB}. 111 * @param filter An optional filter to use to identify which 112 * entries to suppress. If this is {@code null}, 113 * then a default LDAP true filter (which will match 114 * any entry) will be used. 115 * @param excludeMatching Indicates whether to exclude entries that match 116 * the criteria (if {@code true}) or to exclude 117 * entries that do not match the criteria (if 118 * {@code false}). 119 * @param excludedCount An optional counter that will be incremented for 120 * each entry that is excluded. 121 */ 122 public ExcludeEntryTransformation(final Schema schema, final DN baseDN, 123 final SearchScope scope, 124 final Filter filter, 125 final boolean excludeMatching, 126 final AtomicLong excludedCount) 127 { 128 this.excludeMatching = excludeMatching; 129 this.excludedCount = excludedCount; 130 131 132 // If a schema was provided, then use it. Otherwise, use the default 133 // standard schema. 134 Schema s = schema; 135 if (s == null) 136 { 137 try 138 { 139 s = Schema.getDefaultStandardSchema(); 140 } 141 catch (final Exception e) 142 { 143 // This should never happen. 144 Debug.debugException(e); 145 } 146 } 147 this.schema = s; 148 149 150 // If a base DN was provided, then use it. Otherwise, use the null DN. 151 if (baseDN == null) 152 { 153 this.baseDN = DN.NULL_DN; 154 } 155 else 156 { 157 this.baseDN = baseDN; 158 } 159 160 161 // If a scope was provided, then use it. Otherwise, use a subtree scope. 162 if (scope == null) 163 { 164 this.scope = SearchScope.SUB; 165 } 166 else 167 { 168 this.scope = scope; 169 } 170 allEntriesAreInScope = 171 (this.baseDN.isNullDN() && (this.scope == SearchScope.SUB)); 172 173 174 // If a filter was provided, then use it. Otherwise, use an LDAP true 175 // filter. 176 if (filter == null) 177 { 178 this.filter = Filter.createANDFilter(); 179 allEntriesMatchFilter = true; 180 } 181 else 182 { 183 this.filter = filter; 184 if (filter.getFilterType() == Filter.FILTER_TYPE_AND) 185 { 186 allEntriesMatchFilter = (filter.getComponents().length == 0); 187 } 188 else 189 { 190 allEntriesMatchFilter = false; 191 } 192 } 193 } 194 195 196 197 /** 198 * {@inheritDoc} 199 */ 200 @Override() 201 public Entry transformEntry(final Entry e) 202 { 203 if (e == null) 204 { 205 return null; 206 } 207 208 209 // Determine whether the entry is within the configured scope. 210 boolean matchesScope; 211 try 212 { 213 matchesScope = 214 (allEntriesAreInScope || e.matchesBaseAndScope(baseDN, scope)); 215 } 216 catch (final Exception ex) 217 { 218 Debug.debugException(ex); 219 220 // This should only happen if the entry has a malformed DN. In that 221 // case, we'll say that it doesn't match the scope. 222 matchesScope = false; 223 } 224 225 226 // Determine whether the entry matches the suppression filter. 227 boolean matchesFilter; 228 try 229 { 230 matchesFilter = (allEntriesMatchFilter || filter.matchesEntry(e, schema)); 231 } 232 catch (final Exception ex) 233 { 234 Debug.debugException(ex); 235 236 // This should only happen if the filter is one that we can't process at 237 // all or against the target entry. In that case, we'll say that it 238 // doesn't match the filter. 239 matchesFilter = false; 240 } 241 242 243 if (matchesScope && matchesFilter) 244 { 245 if (excludeMatching) 246 { 247 if (excludedCount != null) 248 { 249 excludedCount.incrementAndGet(); 250 } 251 return null; 252 } 253 else 254 { 255 return e; 256 } 257 } 258 else 259 { 260 if (excludeMatching) 261 { 262 return e; 263 } 264 else 265 { 266 if (excludedCount != null) 267 { 268 excludedCount.incrementAndGet(); 269 } 270 return null; 271 } 272 } 273 } 274 275 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override() 281 public Entry translate(final Entry original, final long firstLineNumber) 282 { 283 return transformEntry(original); 284 } 285 286 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override() 292 public Entry translateEntryToWrite(final Entry original) 293 { 294 return transformEntry(original); 295 } 296}