001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.tools;
028 import org.opends.messages.Message;
029
030
031
032 import java.io.IOException;
033 import java.io.OutputStream;
034 import java.io.PrintStream;
035 import java.util.Iterator;
036 import java.util.LinkedHashSet;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.TreeMap;
040
041 import org.opends.server.core.DirectoryServer;
042 import org.opends.server.extensions.ConfigFileHandler;
043 import org.opends.server.protocols.ldap.LDAPResultCode;
044 import org.opends.server.types.Attribute;
045 import org.opends.server.types.AttributeType;
046 import org.opends.server.types.AttributeValue;
047 import org.opends.server.types.DN;
048 import org.opends.server.types.Entry;
049 import org.opends.server.types.ExistingFileBehavior;
050 import org.opends.server.types.LDIFImportConfig;
051 import org.opends.server.types.LDIFExportConfig;
052 import org.opends.server.types.Modification;
053 import org.opends.server.types.ModificationType;
054 import org.opends.server.types.NullOutputStream;
055 import org.opends.server.types.ObjectClass;
056 import org.opends.server.util.LDIFReader;
057 import org.opends.server.util.LDIFWriter;
058 import org.opends.server.util.args.ArgumentException;
059 import org.opends.server.util.args.ArgumentParser;
060 import org.opends.server.util.args.BooleanArgument;
061 import org.opends.server.util.args.StringArgument;
062
063 import static org.opends.messages.ToolMessages.*;
064 import static org.opends.server.tools.ToolConstants.*;
065 import static org.opends.server.util.StaticUtils.*;
066
067
068
069 /**
070 * This class provides a program that may be used to determine the differences
071 * between two LDIF files, generating the output in LDIF change format. There
072 * are several things to note about the operation of this program:
073 * <BR>
074 * <UL>
075 * <LI>This program is only designed for cases in which both LDIF files to be
076 * compared will fit entirely in memory at the same time.</LI>
077 * <LI>This program will only compare live data in the LDIF files and will
078 * ignore comments and other elements that do not have any real impact on
079 * the way that the data is interpreted.</LI>
080 * <LI>The differences will be generated in such a way as to provide the
081 * maximum amount of information, so that there will be enough information
082 * for the changes to be reversed (i.e., it will not use the "replace"
083 * modification type but only the "add" and "delete" types, and contents
084 * of deleted entries will be included as comments).</LI>
085 * </UL>
086 *
087 *
088 * Note
089 * that this is only an option for cases in which both LDIF files can fit in
090 * memory. Also note that this will only compare live data in the LDIF files
091 * and will ignore comments and other elements that do not have any real impact
092 * on the way that the data is interpreted.
093 */
094 public class LDIFDiff
095 {
096 /**
097 * The fully-qualified name of this class.
098 */
099 private static final String CLASS_NAME = "org.opends.server.tools.LDIFDiff";
100
101
102
103 /**
104 * Provides the command line arguments to the <CODE>mainDiff</CODE> method
105 * so that they can be processed.
106 *
107 * @param args The command line arguments provided to this program.
108 */
109 public static void main(String[] args)
110 {
111 int exitCode = mainDiff(args, false, System.out, System.err);
112 if (exitCode != 0)
113 {
114 System.exit(filterExitCode(exitCode));
115 }
116 }
117
118
119
120 /**
121 * Parses the provided command line arguments and performs the appropriate
122 * LDIF diff operation.
123 *
124 * @param args The command line arguments provided to this
125 * program.
126 * @param serverInitialized Indicates whether the Directory Server has
127 * already been initialized (and therefore should
128 * not be initialized a second time).
129 * @param outStream The output stream to use for standard output, or
130 * {@code null} if standard output is not needed.
131 * @param errStream The output stream to use for standard error, or
132 * {@code null} if standard error is not needed.
133 *
134 * @return The return code for this operation. A value of zero indicates
135 * that all processing completed successfully. A nonzero value
136 * indicates that some problem occurred during processing.
137 */
138 public static int mainDiff(String[] args, boolean serverInitialized,
139 OutputStream outStream, OutputStream errStream)
140 {
141 PrintStream out;
142 if (outStream == null)
143 {
144 out = NullOutputStream.printStream();
145 }
146 else
147 {
148 out = new PrintStream(outStream);
149 }
150
151 PrintStream err;
152 if (errStream == null)
153 {
154 err = NullOutputStream.printStream();
155 }
156 else
157 {
158 err = new PrintStream(errStream);
159 }
160
161 BooleanArgument overwriteExisting;
162 BooleanArgument showUsage;
163 BooleanArgument singleValueChanges;
164 StringArgument configClass;
165 StringArgument configFile;
166 StringArgument outputLDIF;
167 StringArgument sourceLDIF;
168 StringArgument targetLDIF;
169
170
171 Message toolDescription = INFO_LDIFDIFF_TOOL_DESCRIPTION.get();
172 ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
173 false);
174 try
175 {
176 sourceLDIF = new StringArgument(
177 "sourceldif", 's', "sourceLDIF", true,
178 false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
179 INFO_LDIFDIFF_DESCRIPTION_SOURCE_LDIF.get());
180 argParser.addArgument(sourceLDIF);
181
182 targetLDIF = new StringArgument(
183 "targetldif", 't', "targetLDIF", true,
184 false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
185 INFO_LDIFDIFF_DESCRIPTION_TARGET_LDIF.get());
186 argParser.addArgument(targetLDIF);
187
188 outputLDIF = new StringArgument(
189 "outputldif", 'o', "outputLDIF", false,
190 false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
191 INFO_LDIFDIFF_DESCRIPTION_OUTPUT_LDIF.get());
192 argParser.addArgument(outputLDIF);
193
194 overwriteExisting =
195 new BooleanArgument(
196 "overwriteexisting", 'O',
197 "overwriteExisting",
198 INFO_LDIFDIFF_DESCRIPTION_OVERWRITE_EXISTING.get());
199 argParser.addArgument(overwriteExisting);
200
201 singleValueChanges =
202 new BooleanArgument(
203 "singlevaluechanges", 'S', "singleValueChanges",
204 INFO_LDIFDIFF_DESCRIPTION_SINGLE_VALUE_CHANGES.get());
205 argParser.addArgument(singleValueChanges);
206
207 configFile = new StringArgument("configfile", 'c', "configFile", false,
208 false, true,
209 INFO_CONFIGFILE_PLACEHOLDER.get(), null,
210 null,
211 INFO_DESCRIPTION_CONFIG_FILE.get());
212 configFile.setHidden(true);
213 argParser.addArgument(configFile);
214
215 configClass = new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
216 OPTION_LONG_CONFIG_CLASS, false,
217 false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
218 ConfigFileHandler.class.getName(), null,
219 INFO_DESCRIPTION_CONFIG_CLASS.get());
220 configClass.setHidden(true);
221 argParser.addArgument(configClass);
222
223 showUsage = new BooleanArgument("showusage", OPTION_SHORT_HELP,
224 OPTION_LONG_HELP,
225 INFO_DESCRIPTION_USAGE.get());
226 argParser.addArgument(showUsage);
227 argParser.setUsageArgument(showUsage);
228 }
229 catch (ArgumentException ae)
230 {
231
232 Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
233 err.println(message);
234 return 1;
235 }
236
237
238 // Parse the command-line arguments provided to the program.
239 try
240 {
241 argParser.parseArguments(args);
242 }
243 catch (ArgumentException ae)
244 {
245
246 Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
247
248 err.println(message);
249 err.println(argParser.getUsage());
250 return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
251 }
252
253
254 // If we should just display usage or version information,
255 // then print it and exit.
256 if (argParser.usageOrVersionDisplayed())
257 {
258 return 0;
259 }
260
261
262 boolean checkSchema = configFile.isPresent();
263 if (! serverInitialized)
264 {
265 // Bootstrap the Directory Server configuration for use as a client.
266 DirectoryServer directoryServer = DirectoryServer.getInstance();
267 directoryServer.bootstrapClient();
268
269
270 // If we're to use the configuration then initialize it, along with the
271 // schema.
272 if (checkSchema)
273 {
274 try
275 {
276 directoryServer.initializeJMX();
277 }
278 catch (Exception e)
279 {
280
281 Message message = ERR_LDIFDIFF_CANNOT_INITIALIZE_JMX.get(
282 String.valueOf(configFile.getValue()),
283 e.getMessage());
284 err.println(message);
285 return 1;
286 }
287
288 try
289 {
290 directoryServer.initializeConfiguration(configClass.getValue(),
291 configFile.getValue());
292 }
293 catch (Exception e)
294 {
295 Message message = ERR_LDIFDIFF_CANNOT_INITIALIZE_CONFIG.get(
296 String.valueOf(configFile.getValue()),
297 e.getMessage());
298 err.println(message);
299 return 1;
300 }
301
302 try
303 {
304 directoryServer.initializeSchema();
305 }
306 catch (Exception e)
307 {
308 Message message = ERR_LDIFDIFF_CANNOT_INITIALIZE_SCHEMA.get(
309 String.valueOf(configFile.getValue()),
310 e.getMessage());
311 err.println(message);
312 return 1;
313 }
314 }
315 }
316
317
318 // Open the source LDIF file and read it into a tree map.
319 LDIFReader reader;
320 LDIFImportConfig importConfig = new LDIFImportConfig(sourceLDIF.getValue());
321 try
322 {
323 reader = new LDIFReader(importConfig);
324 }
325 catch (Exception e)
326 {
327 Message message = ERR_LDIFDIFF_CANNOT_OPEN_SOURCE_LDIF.get(
328 sourceLDIF.getValue(),
329 String.valueOf(e));
330 err.println(message);
331 return 1;
332 }
333
334 TreeMap<DN,Entry> sourceMap = new TreeMap<DN,Entry>();
335 try
336 {
337 while (true)
338 {
339 Entry entry = reader.readEntry(checkSchema);
340 if (entry == null)
341 {
342 break;
343 }
344
345 sourceMap.put(entry.getDN(), entry);
346 }
347 }
348 catch (Exception e)
349 {
350 Message message = ERR_LDIFDIFF_ERROR_READING_SOURCE_LDIF.get(
351 sourceLDIF.getValue(),
352 String.valueOf(e));
353 err.println(message);
354 return 1;
355 }
356 finally
357 {
358 try
359 {
360 reader.close();
361 } catch (Exception e) {}
362 }
363
364
365 // Open the target LDIF file and read it into a tree map.
366 importConfig = new LDIFImportConfig(targetLDIF.getValue());
367 try
368 {
369 reader = new LDIFReader(importConfig);
370 }
371 catch (Exception e)
372 {
373 Message message = ERR_LDIFDIFF_CANNOT_OPEN_TARGET_LDIF.get(
374 targetLDIF.getValue(),
375 String.valueOf(e));
376 err.println(message);
377 return 1;
378 }
379
380 TreeMap<DN,Entry> targetMap = new TreeMap<DN,Entry>();
381 try
382 {
383 while (true)
384 {
385 Entry entry = reader.readEntry(checkSchema);
386 if (entry == null)
387 {
388 break;
389 }
390
391 targetMap.put(entry.getDN(), entry);
392 }
393 }
394 catch (Exception e)
395 {
396 Message message = ERR_LDIFDIFF_ERROR_READING_TARGET_LDIF.get(
397 targetLDIF.getValue(),
398 String.valueOf(e));
399 err.println(message);
400 return 1;
401 }
402 finally
403 {
404 try
405 {
406 reader.close();
407 } catch (Exception e) {}
408 }
409
410
411 // Open the output writer that we'll use to write the differences.
412 LDIFWriter writer;
413 try
414 {
415 LDIFExportConfig exportConfig;
416 if (outputLDIF.isPresent())
417 {
418 if (overwriteExisting.isPresent())
419 {
420 exportConfig = new LDIFExportConfig(outputLDIF.getValue(),
421 ExistingFileBehavior.OVERWRITE);
422 }
423 else
424 {
425 exportConfig = new LDIFExportConfig(outputLDIF.getValue(),
426 ExistingFileBehavior.APPEND);
427 }
428 }
429 else
430 {
431 exportConfig = new LDIFExportConfig(out);
432 }
433
434 writer = new LDIFWriter(exportConfig);
435 }
436 catch (Exception e)
437 {
438 Message message = ERR_LDIFDIFF_CANNOT_OPEN_OUTPUT.get(String.valueOf(e));
439 err.println(message);
440 return 1;
441 }
442
443
444 try
445 {
446 // Check to see if either or both of the source and target maps are empty.
447 if (sourceMap.isEmpty())
448 {
449 if (targetMap.isEmpty())
450 {
451 // They're both empty, so there are no differences.
452 Message message = INFO_LDIFDIFF_NO_DIFFERENCES.get();
453 writer.writeComment(message, 0);
454 return 0;
455 }
456 else
457 {
458 // The target isn't empty, so they're all adds.
459 Iterator<DN> targetIterator = targetMap.keySet().iterator();
460 while (targetIterator.hasNext())
461 {
462 writeAdd(writer, targetMap.get(targetIterator.next()));
463 }
464 return 0;
465 }
466 }
467 else if (targetMap.isEmpty())
468 {
469 // The source isn't empty, so they're all deletes.
470 Iterator<DN> sourceIterator = sourceMap.keySet().iterator();
471 while (sourceIterator.hasNext())
472 {
473 writeDelete(writer, sourceMap.get(sourceIterator.next()));
474 }
475 return 0;
476 }
477 else
478 {
479 // Iterate through all the entries in the source and target maps and
480 // identify the differences.
481 Iterator<DN> sourceIterator = sourceMap.keySet().iterator();
482 Iterator<DN> targetIterator = targetMap.keySet().iterator();
483 DN sourceDN = sourceIterator.next();
484 DN targetDN = targetIterator.next();
485 Entry sourceEntry = sourceMap.get(sourceDN);
486 Entry targetEntry = targetMap.get(targetDN);
487 boolean differenceFound = false;
488
489 while (true)
490 {
491 // Compare the DNs to determine the relative order of the
492 // entries.
493 int comparatorValue = sourceDN.compareTo(targetDN);
494 if (comparatorValue < 0)
495 {
496 // The source entry should be before the target entry, which means
497 // that the source entry has been deleted.
498 writeDelete(writer, sourceEntry);
499 differenceFound = true;
500 if (sourceIterator.hasNext())
501 {
502 sourceDN = sourceIterator.next();
503 sourceEntry = sourceMap.get(sourceDN);
504 }
505 else
506 {
507 // There are no more source entries, so if there are more target
508 // entries then they're all adds.
509 writeAdd(writer, targetEntry);
510
511 while (targetIterator.hasNext())
512 {
513 targetDN = targetIterator.next();
514 targetEntry = targetMap.get(targetDN);
515 writeAdd(writer, targetEntry);
516 differenceFound = true;
517 }
518
519 break;
520 }
521 }
522 else if (comparatorValue > 0)
523 {
524 // The target entry should be before the source entry, which means
525 // that the target entry has been added.
526 writeAdd(writer, targetEntry);
527 differenceFound = true;
528 if (targetIterator.hasNext())
529 {
530 targetDN = targetIterator.next();
531 targetEntry = targetMap.get(targetDN);
532 }
533 else
534 {
535 // There are no more target entries so all of the remaining source
536 // entries are deletes.
537 writeDelete(writer, sourceEntry);
538 differenceFound = true;
539 while (sourceIterator.hasNext())
540 {
541 sourceDN = sourceIterator.next();
542 sourceEntry = sourceMap.get(sourceDN);
543 writeDelete(writer, sourceEntry);
544 }
545
546 break;
547 }
548 }
549 else
550 {
551 // The DNs are the same, so check to see if the entries are the
552 // same or have been modified.
553 if (writeModify(writer, sourceEntry, targetEntry,
554 singleValueChanges.isPresent()))
555 {
556 differenceFound = true;
557 }
558
559 if (sourceIterator.hasNext())
560 {
561 sourceDN = sourceIterator.next();
562 sourceEntry = sourceMap.get(sourceDN);
563 }
564 else
565 {
566 // There are no more source entries, so if there are more target
567 // entries then they're all adds.
568 while (targetIterator.hasNext())
569 {
570 targetDN = targetIterator.next();
571 targetEntry = targetMap.get(targetDN);
572 writeAdd(writer, targetEntry);
573 differenceFound = true;
574 }
575
576 break;
577 }
578
579 if (targetIterator.hasNext())
580 {
581 targetDN = targetIterator.next();
582 targetEntry = targetMap.get(targetDN);
583 }
584 else
585 {
586 // There are no more target entries so all of the remaining source
587 // entries are deletes.
588 writeDelete(writer, sourceEntry);
589 differenceFound = true;
590 while (sourceIterator.hasNext())
591 {
592 sourceDN = sourceIterator.next();
593 sourceEntry = sourceMap.get(sourceDN);
594 writeDelete(writer, sourceEntry);
595 }
596
597 break;
598 }
599 }
600 }
601
602
603 if (! differenceFound)
604 {
605 Message message = INFO_LDIFDIFF_NO_DIFFERENCES.get();
606 writer.writeComment(message, 0);
607 }
608 }
609 }
610 catch (IOException e)
611 {
612 Message message =
613 ERR_LDIFDIFF_ERROR_WRITING_OUTPUT.get(String.valueOf(e));
614 err.println(message);
615 return 1;
616 }
617 finally
618 {
619 try
620 {
621 writer.close();
622 } catch (Exception e) {}
623 }
624
625
626 // If we've gotten to this point, then everything was successful.
627 return 0;
628 }
629
630
631
632 /**
633 * Writes an add change record to the LDIF writer.
634 *
635 * @param writer The writer to which the add record should be written.
636 * @param entry The entry that has been added.
637 *
638 * @throws IOException If a problem occurs while attempting to write the add
639 * record.
640 */
641 private static void writeAdd(LDIFWriter writer, Entry entry)
642 throws IOException
643 {
644 writer.writeAddChangeRecord(entry);
645 writer.flush();
646 }
647
648
649
650 /**
651 * Writes a delete change record to the LDIF writer, including a comment
652 * with the contents of the deleted entry.
653 *
654 * @param writer The writer to which the delete record should be written.
655 * @param entry The entry that has been deleted.
656 *
657 * @throws IOException If a problem occurs while attempting to write the
658 * delete record.
659 */
660 private static void writeDelete(LDIFWriter writer, Entry entry)
661 throws IOException
662 {
663 writer.writeDeleteChangeRecord(entry, true);
664 writer.flush();
665 }
666
667
668
669 /**
670 * Writes a modify change record to the LDIF writer. Note that this will
671 * handle all the necessary logic for determining if the entries are actually
672 * different, and if they are the same then no output will be generated. Also
673 * note that this will only look at differences between the objectclasses and
674 * user attributes. It will ignore differences in the DN and operational
675 * attributes.
676 *
677 * @param writer The writer to which the modify record should be
678 * written.
679 * @param sourceEntry The source form of the entry.
680 * @param targetEntry The target form of the entry.
681 * @param singleValueChanges Indicates whether each attribute-level change
682 * should be written in a separate modification
683 * per attribute value.
684 *
685 * @return <CODE>true</CODE> if there were any differences found between the
686 * source and target entries, or <CODE>false</CODE> if not.
687 *
688 * @throws IOException If a problem occurs while attempting to write the
689 * change record.
690 */
691 private static boolean writeModify(LDIFWriter writer, Entry sourceEntry,
692 Entry targetEntry,
693 boolean singleValueChanges)
694 throws IOException
695 {
696 // Create a list to hold the modifications that are found.
697 LinkedList<Modification> modifications = new LinkedList<Modification>();
698
699
700 // Look at the set of objectclasses for the entries.
701 LinkedHashSet<ObjectClass> sourceClasses =
702 new LinkedHashSet<ObjectClass>(
703 sourceEntry.getObjectClasses().keySet());
704 LinkedHashSet<ObjectClass> targetClasses =
705 new LinkedHashSet<ObjectClass>(
706 targetEntry.getObjectClasses().keySet());
707 Iterator<ObjectClass> sourceClassIterator = sourceClasses.iterator();
708 while (sourceClassIterator.hasNext())
709 {
710 ObjectClass sourceClass = sourceClassIterator.next();
711 if (targetClasses.remove(sourceClass))
712 {
713 sourceClassIterator.remove();
714 }
715 }
716
717 if (! sourceClasses.isEmpty())
718 {
719 // Whatever is left must have been deleted.
720 AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
721 LinkedHashSet<AttributeValue> values =
722 new LinkedHashSet<AttributeValue>();
723 for (ObjectClass oc : sourceClasses)
724 {
725 values.add(new AttributeValue(attrType, oc.getNameOrOID()));
726 }
727
728 Attribute attr = new Attribute(attrType, attrType.getNameOrOID(), values);
729 modifications.add(new Modification(ModificationType.DELETE, attr));
730 }
731
732 if (! targetClasses.isEmpty())
733 {
734 // Whatever is left must have been added.
735 AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
736 LinkedHashSet<AttributeValue> values =
737 new LinkedHashSet<AttributeValue>();
738 for (ObjectClass oc : targetClasses)
739 {
740 values.add(new AttributeValue(attrType, oc.getNameOrOID()));
741 }
742
743 Attribute a = new Attribute(attrType, attrType.getNameOrOID(), values);
744 modifications.add(new Modification(ModificationType.ADD, a));
745 }
746
747
748 // Look at the user attributes for the entries.
749 LinkedHashSet<AttributeType> sourceTypes =
750 new LinkedHashSet<AttributeType>(
751 sourceEntry.getUserAttributes().keySet());
752 Iterator<AttributeType> sourceTypeIterator = sourceTypes.iterator();
753 while (sourceTypeIterator.hasNext())
754 {
755 AttributeType type = sourceTypeIterator.next();
756 List<Attribute> sourceAttrs = sourceEntry.getUserAttribute(type);
757 List<Attribute> targetAttrs = targetEntry.getUserAttribute(type);
758 sourceEntry.removeAttribute(type);
759
760 if (targetAttrs == null)
761 {
762 // The target entry doesn't have this attribute type, so it must have
763 // been deleted. In order to make the delete reversible, delete each
764 // value individually.
765 for (Attribute a : sourceAttrs)
766 {
767 modifications.add(new Modification(ModificationType.DELETE, a));
768 }
769 }
770 else
771 {
772 // Check the attributes for differences. We'll ignore differences in
773 // the order of the values since that isn't significant.
774 targetEntry.removeAttribute(type);
775
776 for (Attribute sourceAttr : sourceAttrs)
777 {
778 Attribute targetAttr = null;
779 Iterator<Attribute> attrIterator = targetAttrs.iterator();
780 while (attrIterator.hasNext())
781 {
782 Attribute a = attrIterator.next();
783 if (a.optionsEqual(sourceAttr.getOptions()))
784 {
785 targetAttr = a;
786 attrIterator.remove();
787 break;
788 }
789 }
790
791 if (targetAttr == null)
792 {
793 // The attribute doesn't exist in the target list, so it has been
794 // deleted.
795 modifications.add(new Modification(ModificationType.DELETE,
796 sourceAttr));
797 }
798 else
799 {
800 // See if the value lists are equal.
801 LinkedHashSet<AttributeValue> sourceValues = sourceAttr.getValues();
802 LinkedHashSet<AttributeValue> targetValues = targetAttr.getValues();
803 LinkedHashSet<AttributeValue> deletedValues =
804 new LinkedHashSet<AttributeValue>();
805 Iterator<AttributeValue> valueIterator = sourceValues.iterator();
806 while (valueIterator.hasNext())
807 {
808 AttributeValue v = valueIterator.next();
809 valueIterator.remove();
810
811 if (! targetValues.remove(v))
812 {
813 // This particular value has been deleted.
814 deletedValues.add(v);
815 }
816 }
817
818 if (! deletedValues.isEmpty())
819 {
820 Attribute attr = new Attribute(type, sourceAttr.getName(),
821 sourceAttr.getOptions(),
822 deletedValues);
823 modifications.add(new Modification(ModificationType.DELETE,
824 attr));
825 }
826
827 // Anything left in the target list has been added.
828 if (! targetValues.isEmpty())
829 {
830 Attribute attr = new Attribute(type, sourceAttr.getName(),
831 sourceAttr.getOptions(),
832 targetValues);
833 modifications.add(new Modification(ModificationType.ADD, attr));
834 }
835 }
836 }
837
838
839 // Any remaining target attributes have been added.
840 for (Attribute targetAttr: targetAttrs)
841 {
842 modifications.add(new Modification(ModificationType.ADD, targetAttr));
843 }
844 }
845 }
846
847 // Any remaining target attribute types have been added.
848 for (AttributeType type : targetEntry.getUserAttributes().keySet())
849 {
850 List<Attribute> targetAttrs = targetEntry.getUserAttribute(type);
851 for (Attribute a : targetAttrs)
852 {
853 modifications.add(new Modification(ModificationType.ADD, a));
854 }
855 }
856
857
858 // Write the modification change record.
859 if (modifications.isEmpty())
860 {
861 return false;
862 }
863 else
864 {
865 if (singleValueChanges)
866 {
867 for (Modification m : modifications)
868 {
869 Attribute a = m.getAttribute();
870 LinkedHashSet<AttributeValue> values = a.getValues();
871 if (values.isEmpty())
872 {
873 LinkedList<Modification> attrMods = new LinkedList<Modification>();
874 attrMods.add(m);
875 writer.writeModifyChangeRecord(sourceEntry.getDN(), attrMods);
876 }
877 else
878 {
879 LinkedList<Modification> attrMods = new LinkedList<Modification>();
880 LinkedHashSet<AttributeValue> valueSet =
881 new LinkedHashSet<AttributeValue>();
882 for (AttributeValue v : values)
883 {
884 valueSet.clear();
885 valueSet.add(v);
886 Attribute attr = new Attribute(a.getAttributeType(),
887 a.getName(), valueSet);
888
889 attrMods.clear();
890 attrMods.add(new Modification(m.getModificationType(), attr));
891 writer.writeModifyChangeRecord(sourceEntry.getDN(), attrMods);
892 }
893 }
894 }
895 }
896 else
897 {
898 writer.writeModifyChangeRecord(sourceEntry.getDN(), modifications);
899 }
900
901 return true;
902 }
903 }
904 }
905