001    /*
002     * Copyright 2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2016 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     */
021    package com.unboundid.ldap.sdk.transformations;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashSet;
029    import java.util.Set;
030    
031    import com.unboundid.ldap.sdk.Attribute;
032    import com.unboundid.ldap.sdk.Entry;
033    import com.unboundid.ldap.sdk.Modification;
034    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
035    import com.unboundid.ldap.sdk.schema.Schema;
036    import com.unboundid.ldif.LDIFAddChangeRecord;
037    import com.unboundid.ldif.LDIFChangeRecord;
038    import com.unboundid.ldif.LDIFModifyChangeRecord;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.StaticUtils;
041    import com.unboundid.util.ThreadSafety;
042    import 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)
055    public 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    }