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.ldap.sdk;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    
028    import com.unboundid.asn1.ASN1Buffer;
029    import com.unboundid.asn1.ASN1BufferSequence;
030    import com.unboundid.asn1.ASN1BufferSet;
031    import com.unboundid.asn1.ASN1Element;
032    import com.unboundid.asn1.ASN1Enumerated;
033    import com.unboundid.asn1.ASN1Exception;
034    import com.unboundid.asn1.ASN1OctetString;
035    import com.unboundid.asn1.ASN1Sequence;
036    import com.unboundid.asn1.ASN1Set;
037    import com.unboundid.asn1.ASN1StreamReader;
038    import com.unboundid.asn1.ASN1StreamReaderSet;
039    import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
040    import com.unboundid.util.Base64;
041    import com.unboundid.util.NotMutable;
042    import com.unboundid.util.ThreadSafety;
043    import com.unboundid.util.ThreadSafetyLevel;
044    
045    import static com.unboundid.ldap.sdk.LDAPMessages.*;
046    import static com.unboundid.util.Debug.*;
047    import static com.unboundid.util.StaticUtils.*;
048    import static com.unboundid.util.Validator.*;
049    
050    
051    
052    /**
053     * This class provides a data structure for holding information about an LDAP
054     * modification, which describes a change to apply to an attribute.  A
055     * modification includes the following elements:
056     * <UL>
057     *   <LI>A modification type, which describes the type of change to apply.</LI>
058     *   <LI>An attribute name, which specifies which attribute should be
059     *       updated.</LI>
060     *   <LI>An optional set of values to use for the modification.</LI>
061     * </UL>
062     */
063    @NotMutable()
064    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065    public final class Modification
066           implements Serializable
067    {
068      /**
069       * The value array that will be used when the modification should not have any
070       * values.
071       */
072      private static final ASN1OctetString[] NO_VALUES = new ASN1OctetString[0];
073    
074    
075    
076      /**
077       * The byte array value array that will be used when the modification does not
078       * have any values.
079       */
080      private static final byte[][] NO_BYTE_VALUES = new byte[0][];
081    
082    
083    
084      /**
085       * The serial version UID for this serializable class.
086       */
087      private static final long serialVersionUID = 5170107037390858876L;
088    
089    
090    
091      // The set of values for this modification.
092      private final ASN1OctetString[] values;
093    
094      // The modification type for this modification.
095      private final ModificationType modificationType;
096    
097      // The name of the attribute to target with this modification.
098      private final String attributeName;
099    
100    
101    
102      /**
103       * Creates a new LDAP modification with the provided modification type and
104       * attribute name.  It will not have any values.
105       *
106       * @param  modificationType  The modification type for this modification.
107       * @param  attributeName     The name of the attribute to target with this
108       *                           modification.  It must not be {@code null}.
109       */
110      public Modification(final ModificationType modificationType,
111                          final String attributeName)
112      {
113        ensureNotNull(attributeName);
114    
115        this.modificationType = modificationType;
116        this.attributeName    = attributeName;
117    
118        values = NO_VALUES;
119      }
120    
121    
122    
123      /**
124       * Creates a new LDAP modification with the provided information.
125       *
126       * @param  modificationType  The modification type for this modification.
127       * @param  attributeName     The name of the attribute to target with this
128       *                           modification.  It must not be {@code null}.
129       * @param  attributeValue    The attribute value for this modification.  It
130       *                           must not be {@code null}.
131       */
132      public Modification(final ModificationType modificationType,
133                          final String attributeName, final String attributeValue)
134      {
135        ensureNotNull(attributeName, attributeValue);
136    
137        this.modificationType = modificationType;
138        this.attributeName    = attributeName;
139    
140        values = new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
141      }
142    
143    
144    
145      /**
146       * Creates a new LDAP modification with the provided information.
147       *
148       * @param  modificationType  The modification type for this modification.
149       * @param  attributeName     The name of the attribute to target with this
150       *                           modification.  It must not be {@code null}.
151       * @param  attributeValue    The attribute value for this modification.  It
152       *                           must not be {@code null}.
153       */
154      public Modification(final ModificationType modificationType,
155                          final String attributeName, final byte[] attributeValue)
156      {
157        ensureNotNull(attributeName, attributeValue);
158    
159        this.modificationType = modificationType;
160        this.attributeName    = attributeName;
161    
162        values = new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
163      }
164    
165    
166    
167      /**
168       * Creates a new LDAP modification with the provided information.
169       *
170       * @param  modificationType  The modification type for this modification.
171       * @param  attributeName     The name of the attribute to target with this
172       *                           modification.  It must not be {@code null}.
173       * @param  attributeValues   The set of attribute value for this modification.
174       *                           It must not be {@code null}.
175       */
176      public Modification(final ModificationType modificationType,
177                          final String attributeName,
178                          final String... attributeValues)
179      {
180        ensureNotNull(attributeName, attributeValues);
181    
182        this.modificationType = modificationType;
183        this.attributeName    = attributeName;
184    
185        values = new ASN1OctetString[attributeValues.length];
186        for (int i=0; i < values.length; i++)
187        {
188          values[i] = new ASN1OctetString(attributeValues[i]);
189        }
190      }
191    
192    
193    
194      /**
195       * Creates a new LDAP modification with the provided information.
196       *
197       * @param  modificationType  The modification type for this modification.
198       * @param  attributeName     The name of the attribute to target with this
199       *                           modification.  It must not be {@code null}.
200       * @param  attributeValues   The set of attribute value for this modification.
201       *                           It must not be {@code null}.
202       */
203      public Modification(final ModificationType modificationType,
204                          final String attributeName,
205                          final byte[]... attributeValues)
206      {
207        ensureNotNull(attributeName, attributeValues);
208    
209        this.modificationType = modificationType;
210        this.attributeName    = attributeName;
211    
212        values = new ASN1OctetString[attributeValues.length];
213        for (int i=0; i < values.length; i++)
214        {
215          values[i] = new ASN1OctetString(attributeValues[i]);
216        }
217      }
218    
219    
220    
221      /**
222       * Creates a new LDAP modification with the provided information.
223       *
224       * @param  modificationType  The modification type for this modification.
225       * @param  attributeName     The name of the attribute to target with this
226       *                           modification.  It must not be {@code null}.
227       * @param  attributeValues   The set of attribute value for this modification.
228       *                           It must not be {@code null}.
229       */
230      public Modification(final ModificationType modificationType,
231                          final String attributeName,
232                          final ASN1OctetString[] attributeValues)
233      {
234        this.modificationType = modificationType;
235        this.attributeName    = attributeName;
236        values                = attributeValues;
237      }
238    
239    
240    
241      /**
242       * Retrieves the modification type for this modification.
243       *
244       * @return  The modification type for this modification.
245       */
246      public ModificationType getModificationType()
247      {
248        return modificationType;
249      }
250    
251    
252    
253      /**
254       * Retrieves the attribute for this modification.
255       *
256       * @return  The attribute for this modification.
257       */
258      public Attribute getAttribute()
259      {
260        return new Attribute(attributeName,
261                             CaseIgnoreStringMatchingRule.getInstance(), values);
262      }
263    
264    
265    
266      /**
267       * Retrieves the name of the attribute to target with this modification.
268       *
269       * @return  The name of the attribute to target with this modification.
270       */
271      public String getAttributeName()
272      {
273        return attributeName;
274      }
275    
276    
277    
278      /**
279       * Indicates whether this modification has at least one value.
280       *
281       * @return  {@code true} if this modification has one or more values, or
282       *          {@code false} if not.
283       */
284      public boolean hasValue()
285      {
286        return (values.length > 0);
287      }
288    
289    
290    
291      /**
292       * Retrieves the set of values for this modification as an array of strings.
293       *
294       * @return  The set of values for this modification as an array of strings.
295       */
296      public String[] getValues()
297      {
298        if (values.length == 0)
299        {
300          return NO_STRINGS;
301        }
302        else
303        {
304          final String[] stringValues = new String[values.length];
305          for (int i=0; i < values.length; i++)
306          {
307            stringValues[i] = values[i].stringValue();
308          }
309    
310          return stringValues;
311        }
312      }
313    
314    
315    
316      /**
317       * Retrieves the set of values for this modification as an array of byte
318       * arrays.
319       *
320       * @return  The set of values for this modification as an array of byte
321       *          arrays.
322       */
323      public byte[][] getValueByteArrays()
324      {
325        if (values.length == 0)
326        {
327          return NO_BYTE_VALUES;
328        }
329        else
330        {
331          final byte[][] byteValues = new byte[values.length][];
332          for (int i=0; i < values.length; i++)
333          {
334            byteValues[i] = values[i].getValue();
335          }
336    
337          return byteValues;
338        }
339      }
340    
341    
342    
343      /**
344       * Retrieves the set of values for this modification as an array of ASN.1
345       * octet strings.
346       *
347       * @return  The set of values for this modification as an array of ASN.1 octet
348       *          strings.
349       */
350      public ASN1OctetString[] getRawValues()
351      {
352        return values;
353      }
354    
355    
356    
357      /**
358       * Writes an ASN.1-encoded representation of this modification to the provided
359       * ASN.1 buffer.
360       *
361       * @param  buffer  The ASN.1 buffer to which the encoded representation should
362       *                 be written.
363       */
364      public void writeTo(final ASN1Buffer buffer)
365      {
366        final ASN1BufferSequence modSequence = buffer.beginSequence();
367        buffer.addEnumerated(modificationType.intValue());
368    
369        final ASN1BufferSequence attrSequence = buffer.beginSequence();
370        buffer.addOctetString(attributeName);
371    
372        final ASN1BufferSet valueSet = buffer.beginSet();
373        for (final ASN1OctetString v : values)
374        {
375          buffer.addElement(v);
376        }
377        valueSet.end();
378        attrSequence.end();
379        modSequence.end();
380      }
381    
382    
383    
384      /**
385       * Encodes this modification to an ASN.1 sequence suitable for use in the LDAP
386       * protocol.
387       *
388       * @return  An ASN.1 sequence containing the encoded value.
389       */
390      public ASN1Sequence encode()
391      {
392        final ASN1Element[] attrElements =
393        {
394          new ASN1OctetString(attributeName),
395          new ASN1Set(values)
396        };
397    
398        final ASN1Element[] modificationElements =
399        {
400          new ASN1Enumerated(modificationType.intValue()),
401          new ASN1Sequence(attrElements)
402        };
403    
404        return new ASN1Sequence(modificationElements);
405      }
406    
407    
408    
409      /**
410       * Reads and decodes an LDAP modification from the provided ASN.1 stream
411       * reader.
412       *
413       * @param  reader  The ASN.1 stream reader from which to read the
414       *                 modification.
415       *
416       * @return  The decoded modification.
417       *
418       * @throws  LDAPException  If a problem occurs while trying to read or decode
419       *                         the modification.
420       */
421      public static Modification readFrom(final ASN1StreamReader reader)
422             throws LDAPException
423      {
424        try
425        {
426          ensureNotNull(reader.beginSequence());
427          final ModificationType modType =
428               ModificationType.valueOf(reader.readEnumerated());
429    
430          ensureNotNull(reader.beginSequence());
431          final String attrName = reader.readString();
432    
433          final ArrayList<ASN1OctetString> valueList =
434               new ArrayList<ASN1OctetString>(5);
435          final ASN1StreamReaderSet valueSet = reader.beginSet();
436          while (valueSet.hasMoreElements())
437          {
438            valueList.add(new ASN1OctetString(reader.readBytes()));
439          }
440    
441          final ASN1OctetString[] values = new ASN1OctetString[valueList.size()];
442          valueList.toArray(values);
443    
444          return new Modification(modType, attrName, values);
445        }
446        catch (Exception e)
447        {
448          debugException(e);
449          throw new LDAPException(ResultCode.DECODING_ERROR,
450               ERR_MOD_CANNOT_DECODE.get(getExceptionMessage(e)), e);
451        }
452      }
453    
454    
455    
456      /**
457       * Decodes the provided ASN.1 sequence as an LDAP modification.
458       *
459       * @param  modificationSequence  The ASN.1 sequence to decode as an LDAP
460       *                               modification.  It must not be {@code null}.
461       *
462       * @return  The decoded LDAP modification.
463       *
464       * @throws  LDAPException  If a problem occurs while trying to decode the
465       *                         provided ASN.1 sequence as an LDAP modification.
466       */
467      public static Modification decode(final ASN1Sequence modificationSequence)
468             throws LDAPException
469      {
470        ensureNotNull(modificationSequence);
471    
472        final ASN1Element[] modificationElements = modificationSequence.elements();
473        if (modificationElements.length != 2)
474        {
475          throw new LDAPException(ResultCode.DECODING_ERROR,
476                                  ERR_MOD_DECODE_INVALID_ELEMENT_COUNT.get(
477                                       modificationElements.length));
478        }
479    
480        final int modType;
481        try
482        {
483          final ASN1Enumerated typeEnumerated =
484               ASN1Enumerated.decodeAsEnumerated(modificationElements[0]);
485          modType = typeEnumerated.intValue();
486        }
487        catch (final ASN1Exception ae)
488        {
489          debugException(ae);
490          throw new LDAPException(ResultCode.DECODING_ERROR,
491               ERR_MOD_DECODE_CANNOT_PARSE_MOD_TYPE.get(getExceptionMessage(ae)),
492               ae);
493        }
494    
495        final ASN1Sequence attrSequence;
496        try
497        {
498          attrSequence = ASN1Sequence.decodeAsSequence(modificationElements[1]);
499        }
500        catch (final ASN1Exception ae)
501        {
502          debugException(ae);
503          throw new LDAPException(ResultCode.DECODING_ERROR,
504               ERR_MOD_DECODE_CANNOT_PARSE_ATTR.get(getExceptionMessage(ae)), ae);
505        }
506    
507        final ASN1Element[] attrElements = attrSequence.elements();
508        if (attrElements.length != 2)
509        {
510          throw new LDAPException(ResultCode.DECODING_ERROR,
511                                  ERR_MOD_DECODE_INVALID_ATTR_ELEMENT_COUNT.get(
512                                       attrElements.length));
513        }
514    
515        final String attrName =
516             ASN1OctetString.decodeAsOctetString(attrElements[0]).stringValue();
517    
518        final ASN1Set valueSet;
519        try
520        {
521          valueSet = ASN1Set.decodeAsSet(attrElements[1]);
522        }
523        catch (final ASN1Exception ae)
524        {
525          debugException(ae);
526          throw new LDAPException(ResultCode.DECODING_ERROR,
527                                  ERR_MOD_DECODE_CANNOT_PARSE_ATTR_VALUE_SET.get(
528                                       getExceptionMessage(ae)), ae);
529        }
530    
531        final ASN1Element[] valueElements = valueSet.elements();
532        final ASN1OctetString[] values = new ASN1OctetString[valueElements.length];
533        for (int i=0; i < values.length; i++)
534        {
535          values[i] = ASN1OctetString.decodeAsOctetString(valueElements[i]);
536        }
537    
538        return new Modification(ModificationType.valueOf(modType), attrName,
539                                values);
540      }
541    
542    
543    
544      /**
545       * Calculates a hash code for this LDAP modification.
546       *
547       * @return  The generated hash code for this LDAP modification.
548       */
549      @Override()
550      public int hashCode()
551      {
552        int hashCode = modificationType.intValue() +
553                       toLowerCase(attributeName).hashCode();
554    
555        for (final ASN1OctetString value : values)
556        {
557          hashCode += value.hashCode();
558        }
559    
560        return hashCode;
561      }
562    
563    
564    
565      /**
566       * Indicates whether the provided object is equal to this LDAP modification.
567       * The provided object will only be considered equal if it is an LDAP
568       * modification with the same modification type, attribute name, and set of
569       * values as this LDAP modification.
570       *
571       * @param  o  The object for which to make the determination.
572       *
573       * @return  {@code true} if the provided object is equal to this modification,
574       *          or {@code false} if not.
575       */
576      @Override()
577      public boolean equals(final Object o)
578      {
579        if (o == null)
580        {
581          return false;
582        }
583    
584        if (o == this)
585        {
586          return true;
587        }
588    
589        if (! (o instanceof Modification))
590        {
591          return false;
592        }
593    
594        final Modification mod = (Modification) o;
595        if (modificationType != mod.modificationType)
596        {
597          return false;
598        }
599    
600        if (! attributeName.equalsIgnoreCase(mod.attributeName))
601        {
602          return false;
603        }
604    
605        if (values.length != mod.values.length)
606        {
607          return false;
608        }
609    
610        // Look at the values using a byte-for-byte matching.
611        for (final ASN1OctetString value : values)
612        {
613          boolean found = false;
614          for (int j = 0; j < mod.values.length; j++)
615          {
616            if (value.equalsIgnoreType(mod.values[j]))
617            {
618              found = true;
619              break;
620            }
621          }
622    
623          if (!found)
624          {
625            return false;
626          }
627        }
628    
629        // If we've gotten here, then we can consider the object equal to this LDAP
630        // modification.
631        return true;
632      }
633    
634    
635    
636      /**
637       * Retrieves a string representation of this LDAP modification.
638       *
639       * @return  A string representation of this LDAP modification.
640       */
641      @Override()
642      public String toString()
643      {
644        final StringBuilder buffer = new StringBuilder();
645        toString(buffer);
646        return buffer.toString();
647      }
648    
649    
650    
651      /**
652       * Appends a string representation of this LDAP modification to the provided
653       * buffer.
654       *
655       * @param  buffer  The buffer to which to append the string representation of
656       *                 this LDAP modification.
657       */
658      public void toString(final StringBuilder buffer)
659      {
660        buffer.append("LDAPModification(type=");
661    
662        switch (modificationType.intValue())
663        {
664          case 0:
665            buffer.append("add");
666            break;
667          case 1:
668            buffer.append("delete");
669            break;
670          case 2:
671            buffer.append("replace");
672            break;
673          case 3:
674            buffer.append("increment");
675            break;
676          default:
677            buffer.append(modificationType);
678            break;
679        }
680    
681        buffer.append(", attr=");
682        buffer.append(attributeName);
683    
684        if (values.length == 0)
685        {
686          buffer.append(", values={");
687        }
688        else if (needsBase64Encoding())
689        {
690          buffer.append(", base64Values={'");
691    
692          for (int i=0; i < values.length; i++)
693          {
694            if (i > 0)
695            {
696              buffer.append("', '");
697            }
698    
699            buffer.append(Base64.encode(values[i].getValue()));
700          }
701    
702          buffer.append('\'');
703        }
704        else
705        {
706          buffer.append(", values={'");
707    
708          for (int i=0; i < values.length; i++)
709          {
710            if (i > 0)
711            {
712              buffer.append("', '");
713            }
714    
715            buffer.append(values[i].stringValue());
716          }
717    
718          buffer.append('\'');
719        }
720    
721        buffer.append("})");
722      }
723    
724    
725    
726      /**
727       * Indicates whether this modification needs to be base64-encoded when
728       * represented as LDIF.
729       *
730       * @return  {@code true} if this modification needs to be base64-encoded when
731       *          represented as LDIF, or {@code false} if not.
732       */
733      private boolean needsBase64Encoding()
734      {
735        for (final ASN1OctetString s : values)
736        {
737          if (Attribute.needsBase64Encoding(s.getValue()))
738          {
739            return true;
740          }
741        }
742    
743        return false;
744      }
745    }