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