001/*
002 * Copyright 2007-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-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.ldif;
022
023
024
025import java.util.ArrayList;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1OctetString;
031import com.unboundid.ldap.sdk.ChangeType;
032import com.unboundid.ldap.sdk.Control;
033import com.unboundid.ldap.sdk.LDAPException;
034import com.unboundid.ldap.sdk.LDAPInterface;
035import com.unboundid.ldap.sdk.LDAPResult;
036import com.unboundid.ldap.sdk.Modification;
037import com.unboundid.ldap.sdk.ModifyRequest;
038import com.unboundid.util.ByteStringBuffer;
039import com.unboundid.util.Debug;
040import com.unboundid.util.NotMutable;
041import com.unboundid.util.StaticUtils;
042import com.unboundid.util.ThreadSafety;
043import com.unboundid.util.ThreadSafetyLevel;
044import com.unboundid.util.Validator;
045
046
047
048/**
049 * This class defines an LDIF modify change record, which can be used to
050 * represent an LDAP modify request.  See the documentation for the
051 * {@link LDIFChangeRecord} class for an example demonstrating the process for
052 * interacting with LDIF change records.
053 */
054@NotMutable()
055@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
056public final class LDIFModifyChangeRecord
057       extends LDIFChangeRecord
058{
059  /**
060   * The name of the system property that will be used to indicate whether
061   * to always include a trailing dash after the last change in the LDIF
062   * representation of a modify change record.  By default, the dash will always
063   * be included.
064   */
065  public static final  String PROPERTY_ALWAYS_INCLUDE_TRAILING_DASH =
066       "com.unboundid.ldif.modify.alwaysIncludeTrailingDash";
067
068
069
070  /**
071   * Indicates whether to always include a trailing dash after the last change
072   * in the LDIF representation.
073   */
074  private static boolean alwaysIncludeTrailingDash = true;
075
076
077
078  static
079  {
080    final String propValue =
081         System.getProperty(PROPERTY_ALWAYS_INCLUDE_TRAILING_DASH);
082    if ((propValue != null) && (propValue.equalsIgnoreCase("false")))
083    {
084      alwaysIncludeTrailingDash = false;
085    }
086  }
087
088
089
090  /**
091   * The serial version UID for this serializable class.
092   */
093  private static final long serialVersionUID = -7558098319600288036L;
094
095
096
097  // The set of modifications for this modify change record.
098  private final Modification[] modifications;
099
100
101
102  /**
103   * Creates a new LDIF modify change record with the provided DN and set of
104   * modifications.
105   *
106   * @param  dn             The DN for this LDIF add change record.  It must not
107   *                        be {@code null}.
108   * @param  modifications  The set of modifications for this LDIF modify change
109   *                        record.  It must not be {@code null} or empty.
110   */
111  public LDIFModifyChangeRecord(final String dn,
112                                final Modification... modifications)
113  {
114    this(dn, modifications, null);
115  }
116
117
118
119  /**
120   * Creates a new LDIF modify change record with the provided DN and set of
121   * modifications.
122   *
123   * @param  dn             The DN for this LDIF add change record.  It must not
124   *                        be {@code null}.
125   * @param  modifications  The set of modifications for this LDIF modify change
126   *                        record.  It must not be {@code null} or empty.
127   * @param  controls       The set of controls for this LDIF modify change
128   *                        record.  It may be {@code null} or empty if there
129   *                        are no controls.
130   */
131  public LDIFModifyChangeRecord(final String dn,
132                                final Modification[] modifications,
133                                final List<Control> controls)
134  {
135    super(dn, controls);
136
137    Validator.ensureNotNull(modifications);
138    Validator.ensureTrue(modifications.length > 0,
139         "LDIFModifyChangeRecord.modifications must not be empty.");
140
141    this.modifications = modifications;
142  }
143
144
145
146  /**
147   * Creates a new LDIF modify change record with the provided DN and set of
148   * modifications.
149   *
150   * @param  dn             The DN for this LDIF add change record.  It must not
151   *                        be {@code null}.
152   * @param  modifications  The set of modifications for this LDIF modify change
153   *                        record.  It must not be {@code null} or empty.
154   */
155  public LDIFModifyChangeRecord(final String dn,
156                                final List<Modification> modifications)
157  {
158    this(dn, modifications, null);
159  }
160
161
162
163  /**
164   * Creates a new LDIF modify change record with the provided DN and set of
165   * modifications.
166   *
167   * @param  dn             The DN for this LDIF add change record.  It must not
168   *                        be {@code null}.
169   * @param  modifications  The set of modifications for this LDIF modify change
170   *                        record.  It must not be {@code null} or empty.
171   * @param  controls       The set of controls for this LDIF modify change
172   *                        record.  It may be {@code null} or empty if there
173   *                        are no controls.
174   */
175  public LDIFModifyChangeRecord(final String dn,
176                                final List<Modification> modifications,
177                                final List<Control> controls)
178  {
179    super(dn, controls);
180
181    Validator.ensureNotNull(modifications);
182    Validator.ensureFalse(modifications.isEmpty(),
183         "LDIFModifyChangeRecord.modifications must not be empty.");
184
185    this.modifications = new Modification[modifications.size()];
186    modifications.toArray(this.modifications);
187  }
188
189
190
191  /**
192   * Creates a new LDIF modify change record from the provided modify request.
193   *
194   * @param  modifyRequest  The modify request to use to create this LDIF modify
195   *                        change record.  It must not be {@code null}.
196   */
197  public LDIFModifyChangeRecord(final ModifyRequest modifyRequest)
198  {
199    super(modifyRequest.getDN(), modifyRequest.getControlList());
200
201    final List<Modification> mods = modifyRequest.getModifications();
202    modifications = new Modification[mods.size()];
203
204    final Iterator<Modification> iterator = mods.iterator();
205    for (int i=0; i < modifications.length; i++)
206    {
207      modifications[i] = iterator.next();
208    }
209  }
210
211
212
213  /**
214   * Indicates whether the LDIF representation of a modify change record should
215   * always include a trailing dash after the last (or only) change.
216   *
217   * @return  {@code true} if the LDIF representation of a modify change record
218   *          should always include a trailing dash after the last (or only)
219   *          change, or {@code false} if not.
220   */
221  public static boolean alwaysIncludeTrailingDash()
222  {
223    return alwaysIncludeTrailingDash;
224  }
225
226
227
228  /**
229   * Specifies whether the LDIF representation of a modify change record should
230   * always include a trailing dash after the last (or only) change.
231   *
232   * @param  alwaysIncludeTrailingDash  Indicates whether the LDIF
233   *                                    representation of a modify change record
234   *                                    should always include a trailing dash
235   *                                    after the last (or only) change.
236   */
237  public static void setAlwaysIncludeTrailingDash(
238                          final boolean alwaysIncludeTrailingDash)
239  {
240    LDIFModifyChangeRecord.alwaysIncludeTrailingDash =
241         alwaysIncludeTrailingDash;
242  }
243
244
245
246  /**
247   * Retrieves the set of modifications for this modify change record.
248   *
249   * @return  The set of modifications for this modify change record.
250   */
251  public Modification[] getModifications()
252  {
253    return modifications;
254  }
255
256
257
258  /**
259   * Creates a modify request from this LDIF modify change record.  Any change
260   * record controls will be included in the request
261   *
262   * @return  The modify request created from this LDIF modify change record.
263   */
264  public ModifyRequest toModifyRequest()
265  {
266    return toModifyRequest(true);
267  }
268
269
270
271  /**
272   * Creates a modify request from this LDIF modify change record, optionally
273   * including any change record controls in the request.
274   *
275   * @param  includeControls  Indicates whether to include any controls in the
276   *                          request.
277   *
278   * @return  The modify request created from this LDIF modify change record.
279   */
280  public ModifyRequest toModifyRequest(final boolean includeControls)
281  {
282    final ModifyRequest modifyRequest =
283         new ModifyRequest(getDN(), modifications);
284    if (includeControls)
285    {
286      modifyRequest.setControls(getControls());
287    }
288
289    return modifyRequest;
290  }
291
292
293
294  /**
295   * {@inheritDoc}
296   */
297  @Override()
298  public ChangeType getChangeType()
299  {
300    return ChangeType.MODIFY;
301  }
302
303
304
305  /**
306   * {@inheritDoc}
307   */
308  @Override()
309  public LDIFModifyChangeRecord duplicate(final Control... controls)
310  {
311    return new LDIFModifyChangeRecord(getDN(), modifications,
312         StaticUtils.toList(controls));
313  }
314
315
316
317  /**
318   * {@inheritDoc}
319   */
320  @Override()
321  public LDAPResult processChange(final LDAPInterface connection,
322                                  final boolean includeControls)
323         throws LDAPException
324  {
325    return connection.modify(toModifyRequest(includeControls));
326  }
327
328
329
330  /**
331   * {@inheritDoc}
332   */
333  @Override()
334  public String[] toLDIF(final int wrapColumn)
335  {
336    List<String> ldifLines = new ArrayList<>(modifications.length*4);
337    encodeNameAndValue("dn", new ASN1OctetString(getDN()), ldifLines);
338
339    for (final Control c : getControls())
340    {
341      encodeNameAndValue("control", encodeControlString(c), ldifLines);
342    }
343
344    ldifLines.add("changetype: modify");
345
346    for (int i=0; i < modifications.length; i++)
347    {
348      final String attrName = modifications[i].getAttributeName();
349
350      switch (modifications[i].getModificationType().intValue())
351      {
352        case 0:
353          ldifLines.add("add: " + attrName);
354          break;
355        case 1:
356          ldifLines.add("delete: " + attrName);
357          break;
358        case 2:
359          ldifLines.add("replace: " + attrName);
360          break;
361        case 3:
362          ldifLines.add("increment: " + attrName);
363          break;
364        default:
365          // This should never happen.
366          continue;
367      }
368
369      for (final ASN1OctetString value : modifications[i].getRawValues())
370      {
371        encodeNameAndValue(attrName, value, ldifLines);
372      }
373
374      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
375      {
376        ldifLines.add("-");
377      }
378    }
379
380    if (wrapColumn > 2)
381    {
382      ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
383    }
384
385    final String[] ldifArray = new String[ldifLines.size()];
386    ldifLines.toArray(ldifArray);
387    return ldifArray;
388  }
389
390
391
392  /**
393   * {@inheritDoc}
394   */
395  @Override()
396  public void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
397  {
398    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer,
399         wrapColumn);
400    buffer.append(StaticUtils.EOL_BYTES);
401
402    for (final Control c : getControls())
403    {
404      LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer,
405           wrapColumn);
406      buffer.append(StaticUtils.EOL_BYTES);
407    }
408
409    LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"),
410                                  buffer, wrapColumn);
411    buffer.append(StaticUtils.EOL_BYTES);
412
413    for (int i=0; i < modifications.length; i++)
414    {
415      final String attrName = modifications[i].getAttributeName();
416
417      switch (modifications[i].getModificationType().intValue())
418      {
419        case 0:
420          LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName),
421                                        buffer, wrapColumn);
422          buffer.append(StaticUtils.EOL_BYTES);
423          break;
424        case 1:
425          LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName),
426                                        buffer, wrapColumn);
427          buffer.append(StaticUtils.EOL_BYTES);
428          break;
429        case 2:
430          LDIFWriter.encodeNameAndValue("replace",
431                                        new ASN1OctetString(attrName), buffer,
432                                        wrapColumn);
433          buffer.append(StaticUtils.EOL_BYTES);
434          break;
435        case 3:
436          LDIFWriter.encodeNameAndValue("increment",
437                                        new ASN1OctetString(attrName), buffer,
438                                        wrapColumn);
439          buffer.append(StaticUtils.EOL_BYTES);
440          break;
441        default:
442          // This should never happen.
443          continue;
444      }
445
446      for (final ASN1OctetString value : modifications[i].getRawValues())
447      {
448        LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn);
449        buffer.append(StaticUtils.EOL_BYTES);
450      }
451
452      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
453      {
454        buffer.append('-');
455        buffer.append(StaticUtils.EOL_BYTES);
456      }
457    }
458  }
459
460
461
462  /**
463   * {@inheritDoc}
464   */
465  @Override()
466  public void toLDIFString(final StringBuilder buffer, final int wrapColumn)
467  {
468    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer,
469         wrapColumn);
470    buffer.append(StaticUtils.EOL);
471
472    for (final Control c : getControls())
473    {
474      LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer,
475           wrapColumn);
476      buffer.append(StaticUtils.EOL);
477    }
478
479    LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"),
480                                  buffer, wrapColumn);
481    buffer.append(StaticUtils.EOL);
482
483    for (int i=0; i < modifications.length; i++)
484    {
485      final String attrName = modifications[i].getAttributeName();
486
487      switch (modifications[i].getModificationType().intValue())
488      {
489        case 0:
490          LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName),
491                                        buffer, wrapColumn);
492          buffer.append(StaticUtils.EOL);
493          break;
494        case 1:
495          LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName),
496                                        buffer, wrapColumn);
497          buffer.append(StaticUtils.EOL);
498          break;
499        case 2:
500          LDIFWriter.encodeNameAndValue("replace",
501                                        new ASN1OctetString(attrName), buffer,
502                                        wrapColumn);
503          buffer.append(StaticUtils.EOL);
504          break;
505        case 3:
506          LDIFWriter.encodeNameAndValue("increment",
507                                        new ASN1OctetString(attrName), buffer,
508                                        wrapColumn);
509          buffer.append(StaticUtils.EOL);
510          break;
511        default:
512          // This should never happen.
513          continue;
514      }
515
516      for (final ASN1OctetString value : modifications[i].getRawValues())
517      {
518        LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn);
519        buffer.append(StaticUtils.EOL);
520      }
521
522      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
523      {
524        buffer.append('-');
525        buffer.append(StaticUtils.EOL);
526      }
527    }
528  }
529
530
531
532  /**
533   * {@inheritDoc}
534   */
535  @Override()
536  public int hashCode()
537  {
538    int hashCode;
539    try
540    {
541      hashCode = getParsedDN().hashCode();
542    }
543    catch (final Exception e)
544    {
545      Debug.debugException(e);
546      hashCode = StaticUtils.toLowerCase(getDN()).hashCode();
547    }
548
549    for (final Modification m : modifications)
550    {
551      hashCode += m.hashCode();
552    }
553
554    return hashCode;
555  }
556
557
558
559  /**
560   * {@inheritDoc}
561   */
562  @Override()
563  public boolean equals(final Object o)
564  {
565    if (o == null)
566    {
567      return false;
568    }
569
570    if (o == this)
571    {
572      return true;
573    }
574
575    if (! (o instanceof LDIFModifyChangeRecord))
576    {
577      return false;
578    }
579
580    final LDIFModifyChangeRecord r = (LDIFModifyChangeRecord) o;
581
582    final HashSet<Control> c1 = new HashSet<>(getControls());
583    final HashSet<Control> c2 = new HashSet<>(r.getControls());
584    if (! c1.equals(c2))
585    {
586      return false;
587    }
588
589    try
590    {
591      if (! getParsedDN().equals(r.getParsedDN()))
592      {
593        return false;
594      }
595    }
596    catch (final Exception e)
597    {
598      Debug.debugException(e);
599      if (! StaticUtils.toLowerCase(getDN()).equals(
600           StaticUtils.toLowerCase(r.getDN())))
601      {
602        return false;
603      }
604    }
605
606    if (modifications.length != r.modifications.length)
607    {
608      return false;
609    }
610
611    for (int i=0; i < modifications.length; i++)
612    {
613      if (! modifications[i].equals(r.modifications[i]))
614      {
615        return false;
616      }
617    }
618
619    return true;
620  }
621
622
623
624  /**
625   * {@inheritDoc}
626   */
627  @Override()
628  public void toString(final StringBuilder buffer)
629  {
630    buffer.append("LDIFModifyChangeRecord(dn='");
631    buffer.append(getDN());
632    buffer.append("', mods={");
633
634    for (int i=0; i < modifications.length; i++)
635    {
636      if (i > 0)
637      {
638        buffer.append(", ");
639      }
640      modifications[i].toString(buffer);
641    }
642    buffer.append('}');
643
644    final List<Control> controls = getControls();
645    if (! controls.isEmpty())
646    {
647      buffer.append(", controls={");
648
649      final Iterator<Control> iterator = controls.iterator();
650      while (iterator.hasNext())
651      {
652        iterator.next().toString(buffer);
653        if (iterator.hasNext())
654        {
655          buffer.append(',');
656        }
657      }
658
659      buffer.append('}');
660    }
661
662    buffer.append(')');
663  }
664}