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