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