001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 }