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.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.Set; 045 046import com.unboundid.ldap.sdk.Attribute; 047import com.unboundid.ldap.sdk.Entry; 048import com.unboundid.ldap.sdk.Modification; 049import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 050import com.unboundid.ldap.sdk.schema.Schema; 051import com.unboundid.ldif.LDIFAddChangeRecord; 052import com.unboundid.ldif.LDIFChangeRecord; 053import com.unboundid.ldif.LDIFModifyChangeRecord; 054import com.unboundid.util.Debug; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060 061 062 063/** 064 * This class provides an implementation of an entry and LDIF change record 065 * transformation that will remove a specified set of attributes from entries 066 * or change records. Note that this transformation will not alter entry DNs, 067 * so if an attribute to exclude is included in an entry's DN, that value will 068 * still be visible in the DN even if it is removed from the set of attributes 069 * in the entry. 070 */ 071@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 072public final class ExcludeAttributeTransformation 073 implements EntryTransformation, LDIFChangeRecordTransformation 074{ 075 // The schema to use when processing. 076 @Nullable private final Schema schema; 077 078 // The set of attributes to exclude from entries. 079 @NotNull private final Set<String> attributes; 080 081 082 083 /** 084 * Creates a new exclude attribute transformation that will strip the 085 * specified attributes out of entries and change records. 086 * 087 * @param schema The scheme to use to identify alternate names that 088 * may be used to reference the attributes to exclude from 089 * entries. It may be {@code null} to use a default 090 * standard schema. 091 * @param attributes The names of the attributes to strip from entries and 092 * change records. It must not be {@code null} or empty. 093 */ 094 public ExcludeAttributeTransformation(@Nullable final Schema schema, 095 @NotNull final String... attributes) 096 { 097 this(schema, StaticUtils.toList(attributes)); 098 } 099 100 101 102 /** 103 * Creates a new exclude attribute transformation that will strip the 104 * specified attributes out of entries and change records. 105 * 106 * @param schema The scheme to use to identify alternate names that 107 * may be used to reference the attributes to exclude from 108 * entries. It may be {@code null} to use a default 109 * standard schema. 110 * @param attributes The names of the attributes to strip from entries and 111 * change records. It must not be {@code null} or empty. 112 */ 113 public ExcludeAttributeTransformation(@Nullable final Schema schema, 114 @NotNull final Collection<String> attributes) 115 { 116 // If a schema was provided, then use it. Otherwise, use the default 117 // standard schema. 118 Schema s = schema; 119 if (s == null) 120 { 121 try 122 { 123 s = Schema.getDefaultStandardSchema(); 124 } 125 catch (final Exception e) 126 { 127 // This should never happen. 128 Debug.debugException(e); 129 } 130 } 131 this.schema = s; 132 133 134 // Identify all of the names that may be used to reference the attributes 135 // to suppress. 136 final HashSet<String> attrNames = 137 new HashSet<>(StaticUtils.computeMapCapacity(3*attributes.size())); 138 for (final String attrName : attributes) 139 { 140 final String baseName = 141 Attribute.getBaseName(StaticUtils.toLowerCase(attrName)); 142 attrNames.add(baseName); 143 144 if (s != null) 145 { 146 final AttributeTypeDefinition at = s.getAttributeType(baseName); 147 if (at != null) 148 { 149 attrNames.add(StaticUtils.toLowerCase(at.getOID())); 150 for (final String name : at.getNames()) 151 { 152 attrNames.add(StaticUtils.toLowerCase(name)); 153 } 154 } 155 } 156 } 157 this.attributes = Collections.unmodifiableSet(attrNames); 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override() 166 @Nullable() 167 public Entry transformEntry(@NotNull final Entry e) 168 { 169 if (e == null) 170 { 171 return null; 172 } 173 174 175 // First, see if the entry has any of the target attributes. If not, we can 176 // just return the provided entry. 177 boolean hasAttributeToRemove = false; 178 final Collection<Attribute> originalAttributes = e.getAttributes(); 179 for (final Attribute a : originalAttributes) 180 { 181 if (attributes.contains(StaticUtils.toLowerCase(a.getBaseName()))) 182 { 183 hasAttributeToRemove = true; 184 break; 185 } 186 } 187 188 if (! hasAttributeToRemove) 189 { 190 return e; 191 } 192 193 194 // Create a copy of the entry with all appropriate attributes removed. 195 final ArrayList<Attribute> attributesToKeep = 196 new ArrayList<>(originalAttributes.size()); 197 for (final Attribute a : originalAttributes) 198 { 199 if (! attributes.contains(StaticUtils.toLowerCase(a.getBaseName()))) 200 { 201 attributesToKeep.add(a); 202 } 203 } 204 205 return new Entry(e.getDN(), schema, attributesToKeep); 206 } 207 208 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override() 214 @Nullable() 215 public LDIFChangeRecord transformChangeRecord( 216 @NotNull final LDIFChangeRecord r) 217 { 218 if (r == null) 219 { 220 return null; 221 } 222 223 224 // If it's an add change record, then just use the same processing as for an 225 // entry, except we will suppress the entire change record if all of the 226 // attributes end up getting suppressed. 227 if (r instanceof LDIFAddChangeRecord) 228 { 229 final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r; 230 final Entry updatedEntry = transformEntry(addRecord.getEntryToAdd()); 231 if (updatedEntry.getAttributes().isEmpty()) 232 { 233 return null; 234 } 235 236 return new LDIFAddChangeRecord(updatedEntry, addRecord.getControls()); 237 } 238 239 240 // If it's a modify change record, then suppress all modifications targeting 241 // any of the appropriate attributes. If there are no more modifications 242 // left, then suppress the entire change record. 243 if (r instanceof LDIFModifyChangeRecord) 244 { 245 final LDIFModifyChangeRecord modifyRecord = (LDIFModifyChangeRecord) r; 246 247 final Modification[] originalMods = modifyRecord.getModifications(); 248 final ArrayList<Modification> modsToKeep = 249 new ArrayList<>(originalMods.length); 250 for (final Modification m : originalMods) 251 { 252 final String attrName = StaticUtils.toLowerCase( 253 Attribute.getBaseName(m.getAttributeName())); 254 if (! attributes.contains(attrName)) 255 { 256 modsToKeep.add(m); 257 } 258 } 259 260 if (modsToKeep.isEmpty()) 261 { 262 return null; 263 } 264 265 return new LDIFModifyChangeRecord(modifyRecord.getDN(), modsToKeep, 266 modifyRecord.getControls()); 267 } 268 269 270 // If it's some other type of change record (which should just be delete or 271 // modify DN), then don't do anything. 272 return r; 273 } 274 275 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override() 281 @Nullable() 282 public Entry translate(@NotNull final Entry original, 283 final long firstLineNumber) 284 { 285 return transformEntry(original); 286 } 287 288 289 290 /** 291 * {@inheritDoc} 292 */ 293 @Override() 294 @Nullable() 295 public LDIFChangeRecord translate(@NotNull final LDIFChangeRecord original, 296 final long firstLineNumber) 297 { 298 return transformChangeRecord(original); 299 } 300 301 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override() 307 @Nullable() 308 public Entry translateEntryToWrite(@NotNull final Entry original) 309 { 310 return transformEntry(original); 311 } 312 313 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override() 319 @Nullable() 320 public LDIFChangeRecord translateChangeRecordToWrite( 321 @NotNull final LDIFChangeRecord original) 322 { 323 return transformChangeRecord(original); 324 } 325}