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