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