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}