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.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 = new HashSet<String>(3*attributes.size());
120    for (final String attrName : attributes)
121    {
122      final String baseName =
123           Attribute.getBaseName(StaticUtils.toLowerCase(attrName));
124      attrNames.add(baseName);
125
126      if (s != null)
127      {
128        final AttributeTypeDefinition at = s.getAttributeType(baseName);
129        if (at != null)
130        {
131          attrNames.add(StaticUtils.toLowerCase(at.getOID()));
132          for (final String name : at.getNames())
133          {
134            attrNames.add(StaticUtils.toLowerCase(name));
135          }
136        }
137      }
138    }
139    this.attributes = Collections.unmodifiableSet(attrNames);
140  }
141
142
143
144  /**
145   * {@inheritDoc}
146   */
147  @Override()
148  public Entry transformEntry(final Entry e)
149  {
150    if (e == null)
151    {
152      return null;
153    }
154
155
156    // First, see if the entry has any of the target attributes.  If not, we can
157    // just return the provided entry.
158    boolean hasAttributeToRemove = false;
159    final Collection<Attribute> originalAttributes = e.getAttributes();
160    for (final Attribute a : originalAttributes)
161    {
162      if (attributes.contains(StaticUtils.toLowerCase(a.getBaseName())))
163      {
164        hasAttributeToRemove = true;
165        break;
166      }
167    }
168
169    if (! hasAttributeToRemove)
170    {
171      return e;
172    }
173
174
175    // Create a copy of the entry with all appropriate attributes removed.
176    final ArrayList<Attribute> attributesToKeep =
177         new ArrayList<Attribute>(originalAttributes.size());
178    for (final Attribute a : originalAttributes)
179    {
180      if (! attributes.contains(StaticUtils.toLowerCase(a.getBaseName())))
181      {
182        attributesToKeep.add(a);
183      }
184    }
185
186    return new Entry(e.getDN(), schema, attributesToKeep);
187  }
188
189
190
191  /**
192   * {@inheritDoc}
193   */
194  @Override()
195  public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
196  {
197    if (r == null)
198    {
199      return null;
200    }
201
202
203    // If it's an add change record, then just use the same processing as for an
204    // entry, except we will suppress the entire change record if all of the
205    // attributes end up getting suppressed.
206    if (r instanceof LDIFAddChangeRecord)
207    {
208      final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
209      final Entry updatedEntry = transformEntry(addRecord.getEntryToAdd());
210      if (updatedEntry.getAttributes().isEmpty())
211      {
212        return null;
213      }
214
215      return new LDIFAddChangeRecord(updatedEntry, addRecord.getControls());
216    }
217
218
219    // If it's a modify change record, then suppress all modifications targeting
220    // any of the appropriate attributes.  If there are no more modifications
221    // left, then suppress the entire change record.
222    if (r instanceof LDIFModifyChangeRecord)
223    {
224      final LDIFModifyChangeRecord modifyRecord = (LDIFModifyChangeRecord) r;
225
226      final Modification[] originalMods = modifyRecord.getModifications();
227      final ArrayList<Modification> modsToKeep =
228           new ArrayList<Modification>(originalMods.length);
229      for (final Modification m : originalMods)
230      {
231        final String attrName = StaticUtils.toLowerCase(
232             Attribute.getBaseName(m.getAttributeName()));
233        if (! attributes.contains(attrName))
234        {
235          modsToKeep.add(m);
236        }
237      }
238
239      if (modsToKeep.isEmpty())
240      {
241        return null;
242      }
243
244      return new LDIFModifyChangeRecord(modifyRecord.getDN(), modsToKeep,
245           modifyRecord.getControls());
246    }
247
248
249    // If it's some other type of change record (which should just be delete or
250    // modify DN), then don't do anything.
251    return r;
252  }
253
254
255
256  /**
257   * {@inheritDoc}
258   */
259  @Override()
260  public Entry translate(final Entry original, final long firstLineNumber)
261  {
262    return transformEntry(original);
263  }
264
265
266
267  /**
268   * {@inheritDoc}
269   */
270  @Override()
271  public LDIFChangeRecord translate(final LDIFChangeRecord original,
272                                    final long firstLineNumber)
273  {
274    return transformChangeRecord(original);
275  }
276
277
278
279  /**
280   * {@inheritDoc}
281   */
282  @Override()
283  public Entry translateEntryToWrite(final Entry original)
284  {
285    return transformEntry(original);
286  }
287
288
289
290  /**
291   * {@inheritDoc}
292   */
293  @Override()
294  public LDIFChangeRecord translateChangeRecordToWrite(
295                               final LDIFChangeRecord original)
296  {
297    return transformChangeRecord(original);
298  }
299}