001/*
002 * Copyright 2007-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 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 LDAPResult processChange(final LDAPInterface connection,
310                                  final boolean includeControls)
311         throws LDAPException
312  {
313    return connection.modify(toModifyRequest(includeControls));
314  }
315
316
317
318  /**
319   * {@inheritDoc}
320   */
321  @Override()
322  public String[] toLDIF(final int wrapColumn)
323  {
324    List<String> ldifLines = new ArrayList<>(modifications.length*4);
325    encodeNameAndValue("dn", new ASN1OctetString(getDN()), ldifLines);
326
327    for (final Control c : getControls())
328    {
329      encodeNameAndValue("control", encodeControlString(c), ldifLines);
330    }
331
332    ldifLines.add("changetype: modify");
333
334    for (int i=0; i < modifications.length; i++)
335    {
336      final String attrName = modifications[i].getAttributeName();
337
338      switch (modifications[i].getModificationType().intValue())
339      {
340        case 0:
341          ldifLines.add("add: " + attrName);
342          break;
343        case 1:
344          ldifLines.add("delete: " + attrName);
345          break;
346        case 2:
347          ldifLines.add("replace: " + attrName);
348          break;
349        case 3:
350          ldifLines.add("increment: " + attrName);
351          break;
352        default:
353          // This should never happen.
354          continue;
355      }
356
357      for (final ASN1OctetString value : modifications[i].getRawValues())
358      {
359        encodeNameAndValue(attrName, value, ldifLines);
360      }
361
362      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
363      {
364        ldifLines.add("-");
365      }
366    }
367
368    if (wrapColumn > 2)
369    {
370      ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
371    }
372
373    final String[] ldifArray = new String[ldifLines.size()];
374    ldifLines.toArray(ldifArray);
375    return ldifArray;
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
385  {
386    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer,
387         wrapColumn);
388    buffer.append(StaticUtils.EOL_BYTES);
389
390    for (final Control c : getControls())
391    {
392      LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer,
393           wrapColumn);
394      buffer.append(StaticUtils.EOL_BYTES);
395    }
396
397    LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"),
398                                  buffer, wrapColumn);
399    buffer.append(StaticUtils.EOL_BYTES);
400
401    for (int i=0; i < modifications.length; i++)
402    {
403      final String attrName = modifications[i].getAttributeName();
404
405      switch (modifications[i].getModificationType().intValue())
406      {
407        case 0:
408          LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName),
409                                        buffer, wrapColumn);
410          buffer.append(StaticUtils.EOL_BYTES);
411          break;
412        case 1:
413          LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName),
414                                        buffer, wrapColumn);
415          buffer.append(StaticUtils.EOL_BYTES);
416          break;
417        case 2:
418          LDIFWriter.encodeNameAndValue("replace",
419                                        new ASN1OctetString(attrName), buffer,
420                                        wrapColumn);
421          buffer.append(StaticUtils.EOL_BYTES);
422          break;
423        case 3:
424          LDIFWriter.encodeNameAndValue("increment",
425                                        new ASN1OctetString(attrName), buffer,
426                                        wrapColumn);
427          buffer.append(StaticUtils.EOL_BYTES);
428          break;
429        default:
430          // This should never happen.
431          continue;
432      }
433
434      for (final ASN1OctetString value : modifications[i].getRawValues())
435      {
436        LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn);
437        buffer.append(StaticUtils.EOL_BYTES);
438      }
439
440      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
441      {
442        buffer.append('-');
443        buffer.append(StaticUtils.EOL_BYTES);
444      }
445    }
446  }
447
448
449
450  /**
451   * {@inheritDoc}
452   */
453  @Override()
454  public void toLDIFString(final StringBuilder buffer, final int wrapColumn)
455  {
456    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer,
457         wrapColumn);
458    buffer.append(StaticUtils.EOL);
459
460    for (final Control c : getControls())
461    {
462      LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer,
463           wrapColumn);
464      buffer.append(StaticUtils.EOL);
465    }
466
467    LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"),
468                                  buffer, wrapColumn);
469    buffer.append(StaticUtils.EOL);
470
471    for (int i=0; i < modifications.length; i++)
472    {
473      final String attrName = modifications[i].getAttributeName();
474
475      switch (modifications[i].getModificationType().intValue())
476      {
477        case 0:
478          LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName),
479                                        buffer, wrapColumn);
480          buffer.append(StaticUtils.EOL);
481          break;
482        case 1:
483          LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName),
484                                        buffer, wrapColumn);
485          buffer.append(StaticUtils.EOL);
486          break;
487        case 2:
488          LDIFWriter.encodeNameAndValue("replace",
489                                        new ASN1OctetString(attrName), buffer,
490                                        wrapColumn);
491          buffer.append(StaticUtils.EOL);
492          break;
493        case 3:
494          LDIFWriter.encodeNameAndValue("increment",
495                                        new ASN1OctetString(attrName), buffer,
496                                        wrapColumn);
497          buffer.append(StaticUtils.EOL);
498          break;
499        default:
500          // This should never happen.
501          continue;
502      }
503
504      for (final ASN1OctetString value : modifications[i].getRawValues())
505      {
506        LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn);
507        buffer.append(StaticUtils.EOL);
508      }
509
510      if (alwaysIncludeTrailingDash || (i < (modifications.length - 1)))
511      {
512        buffer.append('-');
513        buffer.append(StaticUtils.EOL);
514      }
515    }
516  }
517
518
519
520  /**
521   * {@inheritDoc}
522   */
523  @Override()
524  public int hashCode()
525  {
526    int hashCode;
527    try
528    {
529      hashCode = getParsedDN().hashCode();
530    }
531    catch (final Exception e)
532    {
533      Debug.debugException(e);
534      hashCode = StaticUtils.toLowerCase(getDN()).hashCode();
535    }
536
537    for (final Modification m : modifications)
538    {
539      hashCode += m.hashCode();
540    }
541
542    return hashCode;
543  }
544
545
546
547  /**
548   * {@inheritDoc}
549   */
550  @Override()
551  public boolean equals(final Object o)
552  {
553    if (o == null)
554    {
555      return false;
556    }
557
558    if (o == this)
559    {
560      return true;
561    }
562
563    if (! (o instanceof LDIFModifyChangeRecord))
564    {
565      return false;
566    }
567
568    final LDIFModifyChangeRecord r = (LDIFModifyChangeRecord) o;
569
570    final HashSet<Control> c1 = new HashSet<>(getControls());
571    final HashSet<Control> c2 = new HashSet<>(r.getControls());
572    if (! c1.equals(c2))
573    {
574      return false;
575    }
576
577    try
578    {
579      if (! getParsedDN().equals(r.getParsedDN()))
580      {
581        return false;
582      }
583    }
584    catch (final Exception e)
585    {
586      Debug.debugException(e);
587      if (! StaticUtils.toLowerCase(getDN()).equals(
588           StaticUtils.toLowerCase(r.getDN())))
589      {
590        return false;
591      }
592    }
593
594    if (modifications.length != r.modifications.length)
595    {
596      return false;
597    }
598
599    for (int i=0; i < modifications.length; i++)
600    {
601      if (! modifications[i].equals(r.modifications[i]))
602      {
603        return false;
604      }
605    }
606
607    return true;
608  }
609
610
611
612  /**
613   * {@inheritDoc}
614   */
615  @Override()
616  public void toString(final StringBuilder buffer)
617  {
618    buffer.append("LDIFModifyChangeRecord(dn='");
619    buffer.append(getDN());
620    buffer.append("', mods={");
621
622    for (int i=0; i < modifications.length; i++)
623    {
624      if (i > 0)
625      {
626        buffer.append(", ");
627      }
628      modifications[i].toString(buffer);
629    }
630    buffer.append('}');
631
632    final List<Control> controls = getControls();
633    if (! controls.isEmpty())
634    {
635      buffer.append(", controls={");
636
637      final Iterator<Control> iterator = controls.iterator();
638      while (iterator.hasNext())
639      {
640        iterator.next().toString(buffer);
641        if (iterator.hasNext())
642        {
643          buffer.append(',');
644        }
645      }
646
647      buffer.append('}');
648    }
649
650    buffer.append(')');
651  }
652}