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