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