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