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