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