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.matchingrules.DistinguishedNameMatchingRule;
032    import com.unboundid.ldap.matchingrules.MatchingRule;
033    import com.unboundid.ldap.sdk.Attribute;
034    import com.unboundid.ldap.sdk.DN;
035    import com.unboundid.ldap.sdk.Entry;
036    import com.unboundid.ldap.sdk.Modification;
037    import com.unboundid.ldap.sdk.RDN;
038    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
039    import com.unboundid.ldap.sdk.schema.Schema;
040    import com.unboundid.ldif.LDIFAddChangeRecord;
041    import com.unboundid.ldif.LDIFChangeRecord;
042    import com.unboundid.ldif.LDIFDeleteChangeRecord;
043    import com.unboundid.ldif.LDIFModifyChangeRecord;
044    import com.unboundid.ldif.LDIFModifyDNChangeRecord;
045    import com.unboundid.util.Debug;
046    import com.unboundid.util.StaticUtils;
047    import com.unboundid.util.ThreadSafety;
048    import com.unboundid.util.ThreadSafetyLevel;
049    
050    
051    
052    /**
053     * This class provides an implementation of an entry and LDIF change record
054     * translator that will rename a specified attribute so that it uses a different
055     * name.
056     */
057    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
058    public final class RenameAttributeTransformation
059           implements EntryTransformation, LDIFChangeRecordTransformation
060    {
061      // Indicates whether to rename attributes in entry DNs.
062      private final boolean renameInDNs;
063    
064      // The schema that will be used in processing.
065      private final Schema schema;
066    
067      // The names that will be replaced with the target name.
068      private final Set<String> baseSourceNames;
069    
070      // The target name that will be used in place of the source name.
071      private final String baseTargetName;
072    
073    
074    
075      /**
076       * Creates a new rename attribute transformation with the provided
077       * information.
078       *
079       * @param  schema           The schema to use in processing.  If this is
080       *                          {@code null}, a default standard schema will be
081       *                          used.
082       * @param  sourceAttribute  The name of the source attribute to be replaced
083       *                          with the name of the target attribute.  It must
084       *                          not be {@code null}.
085       * @param  targetAttribute  The name of the target attribute to use in place
086       *                          of the source attribute.  It must not be
087       *                          {@code null}.
088       * @param  renameInDNs      Indicates whether to rename attributes contained
089       *                          in DNs.  This includes both in the DN of an entry
090       *                          to be transformed, but also in the values of
091       *                          attributes with a DN syntax.
092       */
093      public RenameAttributeTransformation(final Schema schema,
094                                           final String sourceAttribute,
095                                           final String targetAttribute,
096                                           final boolean renameInDNs)
097      {
098        this.renameInDNs = renameInDNs;
099    
100    
101        // If a schema was provided, then use it.  Otherwise, use the default
102        // standard schema.
103        Schema s = schema;
104        if (s == null)
105        {
106          try
107          {
108            s = Schema.getDefaultStandardSchema();
109          }
110          catch (final Exception e)
111          {
112            // This should never happen.
113            Debug.debugException(e);
114          }
115        }
116        this.schema = s;
117    
118    
119        final HashSet<String> sourceNames = new HashSet<String>(5);
120        final String baseSourceName =
121             StaticUtils.toLowerCase(Attribute.getBaseName(sourceAttribute));
122        sourceNames.add(baseSourceName);
123    
124        if (s != null)
125        {
126          final AttributeTypeDefinition at = s.getAttributeType(baseSourceName);
127          if (at != null)
128          {
129            sourceNames.add(StaticUtils.toLowerCase(at.getOID()));
130            for (final String name : at.getNames())
131            {
132              sourceNames.add(StaticUtils.toLowerCase(name));
133            }
134          }
135        }
136        baseSourceNames = Collections.unmodifiableSet(sourceNames);
137    
138    
139        baseTargetName = Attribute.getBaseName(targetAttribute);
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        final String newDN;
156        if (renameInDNs)
157        {
158          newDN = replaceDN(e.getDN());
159        }
160        else
161        {
162          newDN = e.getDN();
163        }
164    
165    
166        // Iterate through the attributes in the entry and make any appropriate name
167        // replacements.
168        final Collection<Attribute> originalAttributes = e.getAttributes();
169        final ArrayList<Attribute> newAttributes =
170             new ArrayList<Attribute>(originalAttributes.size());
171        for (final Attribute a : originalAttributes)
172        {
173          // Determine if we we should rename this attribute.
174          final String newName;
175          final String baseName = StaticUtils.toLowerCase(a.getBaseName());
176          if (baseSourceNames.contains(baseName))
177          {
178            if (a.hasOptions())
179            {
180              final StringBuilder buffer = new StringBuilder();
181              buffer.append(baseTargetName);
182              for (final String option : a.getOptions())
183              {
184                buffer.append(';');
185                buffer.append(option);
186              }
187              newName = buffer.toString();
188            }
189            else
190            {
191              newName = baseTargetName;
192            }
193          }
194          else
195          {
196            newName = a.getName();
197          }
198    
199    
200          // If we should rename attributes in entry DNs, then see if this
201          // attribute has a DN syntax and if so then process its values.
202          final String[] newValues;
203          if (renameInDNs && (schema != null) &&
204               (MatchingRule.selectEqualityMatchingRule(baseName, schema)
205                    instanceof DistinguishedNameMatchingRule))
206          {
207            final String[] originalValues = a.getValues();
208            newValues = new String[originalValues.length];
209            for (int i=0; i < originalValues.length; i++)
210            {
211              newValues[i] = replaceDN(originalValues[i]);
212            }
213          }
214          else
215          {
216            newValues = a.getValues();
217          }
218    
219          newAttributes.add(new Attribute(newName, schema, newValues));
220        }
221    
222        return new Entry(newDN, newAttributes);
223      }
224    
225    
226    
227      /**
228       * {@inheritDoc}
229       */
230      public LDIFChangeRecord transformChangeRecord(final LDIFChangeRecord r)
231      {
232        if (r == null)
233        {
234          return null;
235        }
236    
237    
238        if (r instanceof LDIFAddChangeRecord)
239        {
240          // Just use the same processing as for an entry.
241          final LDIFAddChangeRecord addRecord = (LDIFAddChangeRecord) r;
242          return new LDIFAddChangeRecord(transformEntry(
243               addRecord.getEntryToAdd()), addRecord.getControls());
244        }
245        if (r instanceof LDIFDeleteChangeRecord)
246        {
247          if (renameInDNs)
248          {
249            return new LDIFDeleteChangeRecord(replaceDN(r.getDN()),
250                 r.getControls());
251          }
252          else
253          {
254            return r;
255          }
256        }
257        else if (r instanceof LDIFModifyChangeRecord)
258        {
259          // Determine the new DN for the change record.
260          final String newDN;
261          final LDIFModifyChangeRecord modRecord = (LDIFModifyChangeRecord) r;
262          if (renameInDNs)
263          {
264            newDN = replaceDN(modRecord.getDN());
265          }
266          else
267          {
268            newDN = modRecord.getDN();
269          }
270    
271    
272          // Iterate through the attributes and perform the appropriate rename
273          // processing
274          final Modification[] originalMods = modRecord.getModifications();
275          final Modification[] newMods = new Modification[originalMods.length];
276          for (int i=0; i < originalMods.length; i++)
277          {
278            final String newName;
279            final Modification m = originalMods[i];
280            final String baseName = StaticUtils.toLowerCase(
281                 Attribute.getBaseName(m.getAttributeName()));
282            if (baseSourceNames.contains(baseName))
283            {
284              final Set<String> options =
285                   Attribute.getOptions(m.getAttributeName());
286              if (options.isEmpty())
287              {
288                newName = baseTargetName;
289              }
290              else
291              {
292                final StringBuilder buffer = new StringBuilder();
293                buffer.append(baseTargetName);
294                for (final String option : options)
295                {
296                  buffer.append(';');
297                  buffer.append(option);
298                }
299                newName = buffer.toString();
300              }
301            }
302            else
303            {
304              newName = m.getAttributeName();
305            }
306    
307            final String[] newValues;
308            if (renameInDNs && (schema != null) &&
309                 (MatchingRule.selectEqualityMatchingRule(baseName, schema)
310                      instanceof DistinguishedNameMatchingRule))
311            {
312              final String[] originalValues = m.getValues();
313              newValues = new String[originalValues.length];
314              for (int j=0; j < originalValues.length; j++)
315              {
316                newValues[j] = replaceDN(originalValues[j]);
317              }
318            }
319            else
320            {
321              newValues = m.getValues();
322            }
323    
324            newMods[i] = new Modification(m.getModificationType(), newName,
325                 newValues);
326          }
327    
328          return new LDIFModifyChangeRecord(newDN, newMods,
329               modRecord.getControls());
330        }
331        else if (r instanceof LDIFModifyDNChangeRecord)
332        {
333          if (renameInDNs)
334          {
335            final LDIFModifyDNChangeRecord modDNRecord =
336                 (LDIFModifyDNChangeRecord) r;
337            return new LDIFModifyDNChangeRecord(replaceDN(modDNRecord.getDN()),
338                 replaceDN(modDNRecord.getNewRDN()), modDNRecord.deleteOldRDN(),
339                 replaceDN(modDNRecord.getNewSuperiorDN()),
340                 modDNRecord.getControls());
341          }
342          else
343          {
344            return r;
345          }
346        }
347        else
348        {
349          // This should never happen.
350          return r;
351        }
352      }
353    
354    
355    
356      /**
357       * Makes any appropriate attribute replacements in the provided DN.
358       *
359       * @param  dn  The DN to process.
360       *
361       * @return  The DN with any appropriate replacements.
362       */
363      private String replaceDN(final String dn)
364      {
365        try
366        {
367          final DN parsedDN = new DN(dn);
368          final RDN[] originalRDNs = parsedDN.getRDNs();
369          final RDN[] newRDNs = new RDN[originalRDNs.length];
370          for (int i=0; i < originalRDNs.length; i++)
371          {
372            final String[] originalNames = originalRDNs[i].getAttributeNames();
373            final String[] newNames = new String[originalNames.length];
374            for (int j=0; j < originalNames.length; j++)
375            {
376              if (baseSourceNames.contains(
377                   StaticUtils.toLowerCase(originalNames[j])))
378              {
379                newNames[j] = baseTargetName;
380              }
381              else
382              {
383                newNames[j] = originalNames[j];
384              }
385            }
386            newRDNs[i] =
387                 new RDN(newNames, originalRDNs[i].getByteArrayAttributeValues());
388          }
389    
390          return new DN(newRDNs).toString();
391        }
392        catch (final Exception e)
393        {
394          Debug.debugException(e);
395          return dn;
396        }
397      }
398    
399    
400    
401      /**
402       * {@inheritDoc}
403       */
404      public Entry translate(final Entry original, final long firstLineNumber)
405      {
406        return transformEntry(original);
407      }
408    
409    
410    
411      /**
412       * {@inheritDoc}
413       */
414      public LDIFChangeRecord translate(final LDIFChangeRecord original,
415                                        final long firstLineNumber)
416      {
417        return transformChangeRecord(original);
418      }
419    
420    
421    
422      /**
423       * {@inheritDoc}
424       */
425      public Entry translateEntryToWrite(final Entry original)
426      {
427        return transformEntry(original);
428      }
429    
430    
431    
432      /**
433       * {@inheritDoc}
434       */
435      public LDIFChangeRecord translateChangeRecordToWrite(
436                                   final LDIFChangeRecord original)
437      {
438        return transformChangeRecord(original);
439      }
440    }