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