001    /*
002     * Copyright 2013-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2013-2015 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.examples;
022    
023    
024    
025    import java.io.OutputStream;
026    import java.util.Collections;
027    import java.util.LinkedHashMap;
028    import java.util.LinkedHashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.TreeMap;
032    import java.util.concurrent.atomic.AtomicLong;
033    
034    import com.unboundid.asn1.ASN1OctetString;
035    import com.unboundid.ldap.sdk.Attribute;
036    import com.unboundid.ldap.sdk.DereferencePolicy;
037    import com.unboundid.ldap.sdk.DN;
038    import com.unboundid.ldap.sdk.Filter;
039    import com.unboundid.ldap.sdk.LDAPConnection;
040    import com.unboundid.ldap.sdk.LDAPException;
041    import com.unboundid.ldap.sdk.LDAPSearchException;
042    import com.unboundid.ldap.sdk.ResultCode;
043    import com.unboundid.ldap.sdk.SearchRequest;
044    import com.unboundid.ldap.sdk.SearchResult;
045    import com.unboundid.ldap.sdk.SearchResultEntry;
046    import com.unboundid.ldap.sdk.SearchResultReference;
047    import com.unboundid.ldap.sdk.SearchResultListener;
048    import com.unboundid.ldap.sdk.SearchScope;
049    import com.unboundid.ldap.sdk.Version;
050    import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
051    import com.unboundid.util.Debug;
052    import com.unboundid.util.LDAPCommandLineTool;
053    import com.unboundid.util.StaticUtils;
054    import com.unboundid.util.ThreadSafety;
055    import com.unboundid.util.ThreadSafetyLevel;
056    import com.unboundid.util.args.ArgumentException;
057    import com.unboundid.util.args.ArgumentParser;
058    import com.unboundid.util.args.DNArgument;
059    import com.unboundid.util.args.FilterArgument;
060    import com.unboundid.util.args.IntegerArgument;
061    import com.unboundid.util.args.StringArgument;
062    
063    
064    
065    /**
066     * This class provides a tool that may be used to identify unique attribute
067     * conflicts (i.e., attributes which are supposed to be unique but for which
068     * some values exist in multiple entries).
069     * <BR><BR>
070     * All of the necessary information is provided using command line arguments.
071     * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
072     * class, as well as the following additional arguments:
073     * <UL>
074     *   <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies the base DN to use
075     *       for the searches.  At least one base DN must be provided.</LI>
076     *   <LI>"-f" {filter}" or "--filter "{filter}" -- specifies an optional
077     *       filter to use for identifying entries across which uniqueness should be
078     *       enforced.  If this is not provided, then all entries containing the
079     *       target attribute(s) will be examined.</LI>
080     *   <LI>"-A {attribute}" or "--attribute {attribute}" -- specifies an attribute
081     *       for which to enforce uniqueness.  At least one unique attribute must be
082     *       provided.</LI>
083     *   <LI>"-m {behavior}" or "--multipleAttributeBehavior {behavior}" --
084     *       specifies the behavior that the tool should exhibit if multiple
085     *       unique attributes are provided.  Allowed values include
086     *       unique-within-each-attribute,
087     *       unique-across-all-attributes-including-in-same-entry, and
088     *       unique-across-all-attributes-except-in-same-entry.</LI>
089     *   <LI>"-z {size}" or "--simplePageSize {size}" -- indicates that the search
090     *       to find entries with unique attributes should use the simple paged
091     *       results control to iterate across entries in fixed-size pages rather
092     *       than trying to use a single search to identify all entries containing
093     *       unique attributes.</LI>
094     * </UL>
095     */
096    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
097    public final class IdentifyUniqueAttributeConflicts
098           extends LDAPCommandLineTool
099           implements SearchResultListener
100    {
101      /**
102       * The unique attribute behavior value that indicates uniqueness should only
103       * be ensured within each attribute.
104       */
105      private static final String BEHAVIOR_UNIQUE_WITHIN_ATTR =
106           "unique-within-each-attribute";
107    
108    
109    
110      /**
111       * The unique attribute behavior value that indicates uniqueness should be
112       * ensured across all attributes, and conflicts will not be allowed across
113       * attributes in the same entry.
114       */
115      private static final String BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME =
116           "unique-across-all-attributes-including-in-same-entry";
117    
118    
119    
120      /**
121       * The unique attribute behavior value that indicates uniqueness should be
122       * ensured across all attributes, except that conflicts will not be allowed
123       * across attributes in the same entry.
124       */
125      private static final String BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME =
126           "unique-across-all-attributes-except-in-same-entry";
127    
128    
129    
130      /**
131       * The serial version UID for this serializable class.
132       */
133      private static final long serialVersionUID = -7506817625818259323L;
134    
135    
136    
137      // The number of entries examined so far.
138      private final AtomicLong entriesExamined;
139    
140      // Indicates whether cross-attribute uniqueness conflicts should be allowed
141      // in the same entry.
142      private boolean allowConflictsInSameEntry;
143    
144      // Indicates whether uniqueness should be enforced across all attributes
145      // rather than within each attribute.
146      private boolean uniqueAcrossAttributes;
147    
148      // The argument used to specify the base DNs to use for searches.
149      private DNArgument baseDNArgument;
150    
151      // The argument used to specify a filter indicating which entries to examine.
152      private FilterArgument filterArgument;
153    
154      // The argument used to specify the search page size.
155      private IntegerArgument pageSizeArgument;
156    
157      // The connection to use for finding unique attribute conflicts.
158      private LDAPConnection findConflictsConnection;
159    
160      // A map with counts of unique attribute conflicts by attribute type.
161      private final Map<String, AtomicLong> conflictCounts;
162    
163      // The names of the attributes for which to find uniqueness conflicts.
164      private String[] attributes;
165    
166      // The set of base DNs to use for the searches.
167      private String[] baseDNs;
168    
169      // The argument used to specify the attributes for which to find uniqueness
170      // conflicts.
171      private StringArgument attributeArgument;
172    
173      // The argument used to specify the behavior that should be exhibited if
174      // multiple attributes are specified.
175      private StringArgument multipleAttributeBehaviorArgument;
176    
177    
178    
179      /**
180       * Parse the provided command line arguments and perform the appropriate
181       * processing.
182       *
183       * @param  args  The command line arguments provided to this program.
184       */
185      public static void main(final String... args)
186      {
187        final ResultCode resultCode = main(args, System.out, System.err);
188        if (resultCode != ResultCode.SUCCESS)
189        {
190          System.exit(resultCode.intValue());
191        }
192      }
193    
194    
195    
196      /**
197       * Parse the provided command line arguments and perform the appropriate
198       * processing.
199       *
200       * @param  args       The command line arguments provided to this program.
201       * @param  outStream  The output stream to which standard out should be
202       *                    written.  It may be {@code null} if output should be
203       *                    suppressed.
204       * @param  errStream  The output stream to which standard error should be
205       *                    written.  It may be {@code null} if error messages
206       *                    should be suppressed.
207       *
208       * @return A result code indicating whether the processing was successful.
209       */
210      public static ResultCode main(final String[] args,
211                                    final OutputStream outStream,
212                                    final OutputStream errStream)
213      {
214        final IdentifyUniqueAttributeConflicts tool =
215             new IdentifyUniqueAttributeConflicts(outStream, errStream);
216        return tool.runTool(args);
217      }
218    
219    
220    
221      /**
222       * Creates a new instance of this tool.
223       *
224       * @param  outStream  The output stream to which standard out should be
225       *                    written.  It may be {@code null} if output should be
226       *                    suppressed.
227       * @param  errStream  The output stream to which standard error should be
228       *                    written.  It may be {@code null} if error messages
229       *                    should be suppressed.
230       */
231      public IdentifyUniqueAttributeConflicts(final OutputStream outStream,
232                                              final OutputStream errStream)
233      {
234        super(outStream, errStream);
235    
236        baseDNArgument = null;
237        filterArgument = null;
238        pageSizeArgument = null;
239        attributeArgument = null;
240        multipleAttributeBehaviorArgument = null;
241        findConflictsConnection = null;
242        allowConflictsInSameEntry = false;
243        uniqueAcrossAttributes = false;
244        attributes = null;
245        baseDNs = null;
246    
247        entriesExamined = new AtomicLong(0L);
248        conflictCounts = new TreeMap<String, AtomicLong>();
249      }
250    
251    
252    
253      /**
254       * Retrieves the name of this tool.  It should be the name of the command used
255       * to invoke this tool.
256       *
257       * @return The name for this tool.
258       */
259      @Override()
260      public String getToolName()
261      {
262        return "identify-unique-attribute-conflicts";
263      }
264    
265    
266    
267      /**
268       * Retrieves a human-readable description for this tool.
269       *
270       * @return A human-readable description for this tool.
271       */
272      @Override()
273      public String getToolDescription()
274      {
275        return "This tool may be used to identify unique attribute conflicts.  " +
276             "That is, it may identify values of one or more attributes which " +
277             "are supposed to exist only in a single entry but are found in " +
278             "multiple entries.";
279      }
280    
281    
282    
283      /**
284       * Retrieves a version string for this tool, if available.
285       *
286       * @return A version string for this tool, or {@code null} if none is
287       *          available.
288       */
289      @Override()
290      public String getToolVersion()
291      {
292        return Version.NUMERIC_VERSION_STRING;
293      }
294    
295    
296    
297      /**
298       * Adds the arguments needed by this command-line tool to the provided
299       * argument parser which are not related to connecting or authenticating to
300       * the directory server.
301       *
302       * @param  parser  The argument parser to which the arguments should be added.
303       *
304       * @throws ArgumentException  If a problem occurs while adding the arguments.
305       */
306      @Override()
307      public void addNonLDAPArguments(final ArgumentParser parser)
308           throws ArgumentException
309      {
310        String description = "The search base DN(s) to use to find entries with " +
311             "attributes for which to find uniqueness conflicts.  At least one " +
312             "base DN must be specified.";
313        baseDNArgument = new DNArgument('b', "baseDN", true, 0, "{dn}",
314             description);
315        parser.addArgument(baseDNArgument);
316    
317        description = "A filter that will be used to identify the set of " +
318             "entries in which to identify uniqueness conflicts.  If this is not " +
319             "specified, then all entries containing the target attribute(s) " +
320             "will be examined.";
321        filterArgument = new FilterArgument('f', "filter", false, 1, "{filter}",
322             description);
323        parser.addArgument(filterArgument);
324    
325        description = "The attribute(s) for which to find missing references.  " +
326             "At least one attribute must be specified, and each attribute " +
327             "must be indexed for equality searches and have values which are DNs.";
328        attributeArgument = new StringArgument('A', "attribute", true, 0, "{attr}",
329             description);
330        parser.addArgument(attributeArgument);
331    
332        description = "Indicates the behavior to exhibit if multiple unique " +
333             "attributes are provided.  Allowed values are '" +
334             BEHAVIOR_UNIQUE_WITHIN_ATTR + "' (indicates that each value only " +
335             "needs to be unique within its own attribute type), '" +
336             BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME + "' (indicates that " +
337             "each value needs to be unique across all of the specified " +
338             "attributes), and '" + BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME +
339             "' (indicates each value needs to be unique across all of the " +
340             "specified attributes, except that multiple attributes in the same " +
341             "entry are allowed to share the same value).";
342        final LinkedHashSet<String> allowedValues = new LinkedHashSet<String>(3);
343        allowedValues.add(BEHAVIOR_UNIQUE_WITHIN_ATTR);
344        allowedValues.add(BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME);
345        allowedValues.add(BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME);
346        multipleAttributeBehaviorArgument = new StringArgument('m',
347             "multipleAttributeBehavior", false, 1, "{behavior}", description,
348             allowedValues, BEHAVIOR_UNIQUE_WITHIN_ATTR);
349        parser.addArgument(multipleAttributeBehaviorArgument);
350    
351        description = "The maximum number of entries to retrieve at a time when " +
352             "attempting to find entries with references to other entries.  This " +
353             "requires that the authenticated user have permission to use the " +
354             "simple paged results control, but it can avoid problems with the " +
355             "server sending entries too quickly for the client to handle.  By " +
356             "default, the simple paged results control will not be used.";
357        pageSizeArgument =
358             new IntegerArgument('z', "simplePageSize", false, 1, "{num}",
359                  description, 1, Integer.MAX_VALUE);
360        parser.addArgument(pageSizeArgument);
361      }
362    
363    
364    
365      /**
366       * Performs the core set of processing for this tool.
367       *
368       * @return  A result code that indicates whether the processing completed
369       *          successfully.
370       */
371      @Override()
372      public ResultCode doToolProcessing()
373      {
374        // Determine the multi-attribute behavior that we should exhibit.
375        final List<String> attrList = attributeArgument.getValues();
376        final String multiAttrBehavior =
377             multipleAttributeBehaviorArgument.getValue();
378        if (attrList.size() > 1)
379        {
380          if (multiAttrBehavior.equalsIgnoreCase(
381               BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME))
382          {
383            uniqueAcrossAttributes = true;
384            allowConflictsInSameEntry = false;
385          }
386          else if (multiAttrBehavior.equalsIgnoreCase(
387               BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME))
388          {
389            uniqueAcrossAttributes = true;
390            allowConflictsInSameEntry = true;
391          }
392          else
393          {
394            uniqueAcrossAttributes = false;
395            allowConflictsInSameEntry = true;
396          }
397        }
398        else
399        {
400          uniqueAcrossAttributes = false;
401          allowConflictsInSameEntry = true;
402        }
403    
404    
405        // Get the string representations of the base DNs.
406        final List<DN> dnList = baseDNArgument.getValues();
407        baseDNs = new String[dnList.size()];
408        for (int i=0; i < baseDNs.length; i++)
409        {
410          baseDNs[i] = dnList.get(i).toString();
411        }
412    
413        // Establish a connection to the target directory server to use for finding
414        // entries with unique attributes.
415        final LDAPConnection findUniqueAttributesConnection;
416        try
417        {
418          findUniqueAttributesConnection = getConnection();
419        }
420        catch (final LDAPException le)
421        {
422          Debug.debugException(le);
423          err("Unable to establish a connection to the directory server:  ",
424               StaticUtils.getExceptionMessage(le));
425          return le.getResultCode();
426        }
427    
428        try
429        {
430          // Establish a connection to use for finding unique attribute conflicts.
431          try
432          {
433            findConflictsConnection = getConnection();
434          }
435          catch (final LDAPException le)
436          {
437            Debug.debugException(le);
438            err("Unable to establish a connection to the directory server:  ",
439                 StaticUtils.getExceptionMessage(le));
440            return le.getResultCode();
441          }
442    
443          // Get the set of attributes for which to ensure uniqueness.
444          attributes = new String[attrList.size()];
445          attrList.toArray(attributes);
446    
447    
448          // Construct a search filter that will be used to find all entries with
449          // unique attributes.
450          Filter filter;
451          if (attributes.length == 1)
452          {
453            filter = Filter.createPresenceFilter(attributes[0]);
454            conflictCounts.put(attributes[0], new AtomicLong(0L));
455          }
456          else
457          {
458            final Filter[] orComps = new Filter[attributes.length];
459            for (int i=0; i < attributes.length; i++)
460            {
461              orComps[i] = Filter.createPresenceFilter(attributes[i]);
462              conflictCounts.put(attributes[i], new AtomicLong(0L));
463            }
464            filter = Filter.createORFilter(orComps);
465          }
466    
467          if (filterArgument.isPresent())
468          {
469            filter = Filter.createANDFilter(filterArgument.getValue(), filter);
470          }
471    
472    
473          // Iterate across all of the search base DNs and perform searches to find
474          // unique attributes.
475          for (final String baseDN : baseDNs)
476          {
477            ASN1OctetString cookie = null;
478            do
479            {
480              final SearchRequest searchRequest = new SearchRequest(this, baseDN,
481                   SearchScope.SUB, filter, attributes);
482              if (pageSizeArgument.isPresent())
483              {
484                searchRequest.addControl(new SimplePagedResultsControl(
485                     pageSizeArgument.getValue(), cookie, false));
486              }
487    
488              SearchResult searchResult;
489              try
490              {
491                searchResult = findUniqueAttributesConnection.search(searchRequest);
492              }
493              catch (final LDAPSearchException lse)
494              {
495                Debug.debugException(lse);
496                searchResult = lse.getSearchResult();
497              }
498    
499              if (searchResult.getResultCode() != ResultCode.SUCCESS)
500              {
501                err("An error occurred while attempting to search for unique " +
502                     "attributes in entries below " + baseDN + ":  " +
503                     searchResult.getDiagnosticMessage());
504                return searchResult.getResultCode();
505              }
506    
507              final SimplePagedResultsControl pagedResultsResponse;
508              try
509              {
510                pagedResultsResponse = SimplePagedResultsControl.get(searchResult);
511              }
512              catch (final LDAPException le)
513              {
514                Debug.debugException(le);
515                err("An error occurred while attempting to decode a simple " +
516                     "paged results response control in the response to a " +
517                     "search for entries below " + baseDN + ":  " +
518                     StaticUtils.getExceptionMessage(le));
519                return le.getResultCode();
520              }
521    
522              if (pagedResultsResponse != null)
523              {
524                if (pagedResultsResponse.moreResultsToReturn())
525                {
526                  cookie = pagedResultsResponse.getCookie();
527                }
528                else
529                {
530                  cookie = null;
531                }
532              }
533            }
534            while (cookie != null);
535          }
536    
537    
538          // See if there were any missing references found.
539          boolean conflictFound = false;
540          for (final Map.Entry<String,AtomicLong> e : conflictCounts.entrySet())
541          {
542            final long numConflicts = e.getValue().get();
543            if (numConflicts > 0L)
544            {
545              if (! conflictFound)
546              {
547                err();
548                conflictFound = true;
549              }
550    
551              err("Found " + numConflicts +
552                   " unique value conflicts in attribute " + e.getKey());
553            }
554          }
555    
556          if (conflictFound)
557          {
558            return ResultCode.CONSTRAINT_VIOLATION;
559          }
560          else
561          {
562            out("No unique attribute conflicts were found.");
563            return ResultCode.SUCCESS;
564          }
565        }
566        finally
567        {
568          findUniqueAttributesConnection.close();
569    
570          if (findConflictsConnection != null)
571          {
572            findConflictsConnection.close();
573          }
574        }
575      }
576    
577    
578    
579      /**
580       * Retrieves a map that correlates the number of missing references found by
581       * attribute type.
582       *
583       * @return  A map that correlates the number of missing references found by
584       *          attribute type.
585       */
586      public Map<String,AtomicLong> getConflictCounts()
587      {
588        return Collections.unmodifiableMap(conflictCounts);
589      }
590    
591    
592    
593      /**
594       * Retrieves a set of information that may be used to generate example usage
595       * information.  Each element in the returned map should consist of a map
596       * between an example set of arguments and a string that describes the
597       * behavior of the tool when invoked with that set of arguments.
598       *
599       * @return  A set of information that may be used to generate example usage
600       *          information.  It may be {@code null} or empty if no example usage
601       *          information is available.
602       */
603      @Override()
604      public LinkedHashMap<String[],String> getExampleUsages()
605      {
606        final LinkedHashMap<String[],String> exampleMap =
607             new LinkedHashMap<String[],String>(1);
608    
609        final String[] args =
610        {
611          "--hostname", "server.example.com",
612          "--port", "389",
613          "--bindDN", "uid=john.doe,ou=People,dc=example,dc=com",
614          "--bindPassword", "password",
615          "--baseDN", "dc=example,dc=com",
616          "--attribute", "uid",
617          "--simplePageSize", "100"
618        };
619        exampleMap.put(args,
620             "Identify any values of the uid attribute that are not unique " +
621                  "across all entries below dc=example,dc=com.");
622    
623        return exampleMap;
624      }
625    
626    
627    
628      /**
629       * Indicates that the provided search result entry has been returned by the
630       * server and may be processed by this search result listener.
631       *
632       * @param  searchEntry  The search result entry that has been returned by the
633       *                      server.
634       */
635      public void searchEntryReturned(final SearchResultEntry searchEntry)
636      {
637        try
638        {
639          // If we need to check for conflicts in the same entry, then do that
640          // first.
641          if (! allowConflictsInSameEntry)
642          {
643            boolean conflictFound = false;
644            for (int i=0; i < attributes.length; i++)
645            {
646              final List<Attribute> l1 =
647                   searchEntry.getAttributesWithOptions(attributes[i], null);
648              if (l1 != null)
649              {
650                for (int j=i+1; j < attributes.length; j++)
651                {
652                  final List<Attribute> l2 =
653                       searchEntry.getAttributesWithOptions(attributes[j], null);
654                  if (l2 != null)
655                  {
656                    for (final Attribute a1 : l1)
657                    {
658                      for (final String value : a1.getValues())
659                      {
660                        for (final Attribute a2 : l2)
661                        {
662                          if (a2.hasValue(value))
663                          {
664                            err("Value '", value, "' in attribute ", a1.getName(),
665                                 " of entry '", searchEntry.getDN(),
666                                 " is also present in attribute ", a2.getName(),
667                                 " of the same entry.");
668                            conflictFound = true;
669                            conflictCounts.get(attributes[i]).incrementAndGet();
670                          }
671                        }
672                      }
673                    }
674                  }
675                }
676              }
677            }
678    
679            if (conflictFound)
680            {
681              return;
682            }
683          }
684    
685    
686          // Get the unique attributes from the entry and search for conflicts with
687          // each value in other entries.  Although we could theoretically do this
688          // with fewer searches, most uses of unique attributes don't have multiple
689          // values, so the following code (which is much simpler) is just as
690          // efficient in the common case.
691          for (final String attrName : attributes)
692          {
693            final List<Attribute> attrList =
694                 searchEntry.getAttributesWithOptions(attrName, null);
695            for (final Attribute a : attrList)
696            {
697              for (final String value : a.getValues())
698              {
699                Filter filter;
700                if (uniqueAcrossAttributes)
701                {
702                  final Filter[] orComps = new Filter[attributes.length];
703                  for (int i=0; i < attributes.length; i++)
704                  {
705                    orComps[i] = Filter.createEqualityFilter(attributes[i], value);
706                  }
707                  filter = Filter.createORFilter(orComps);
708                }
709                else
710                {
711                  filter = Filter.createEqualityFilter(attrName, value);
712                }
713    
714                if (filterArgument.isPresent())
715                {
716                  filter = Filter.createANDFilter(filterArgument.getValue(),
717                       filter);
718                }
719    
720    baseDNLoop:
721                for (final String baseDN : baseDNs)
722                {
723                  SearchResult searchResult;
724                  try
725                  {
726                    searchResult = findConflictsConnection.search(baseDN,
727                         SearchScope.SUB, DereferencePolicy.NEVER, 2, 0, false,
728                         filter, "1.1");
729                  }
730                  catch (final LDAPSearchException lse)
731                  {
732                    Debug.debugException(lse);
733                    searchResult = lse.getSearchResult();
734                  }
735    
736                  for (final SearchResultEntry e : searchResult.getSearchEntries())
737                  {
738                    try
739                    {
740                      if (DN.equals(searchEntry.getDN(), e.getDN()))
741                      {
742                        continue;
743                      }
744                    }
745                    catch (final Exception ex)
746                    {
747                      Debug.debugException(ex);
748                    }
749    
750                    err("Value '", value, "' in attribute ", a.getName(),
751                         " of entry '" + searchEntry.getDN(),
752                         "' is also present in entry '", e.getDN(), "'.");
753                    conflictCounts.get(attrName).incrementAndGet();
754                    break baseDNLoop;
755                  }
756    
757                  if (searchResult.getResultCode() != ResultCode.SUCCESS)
758                  {
759                    err("An error occurred while attempting to search for " +
760                         "conflicts with " + a.getName() + " value '" + value +
761                         "' (as found in entry '" + searchEntry.getDN() +
762                         "') below '" + baseDN + "':  " +
763                         searchResult.getDiagnosticMessage());
764                    conflictCounts.get(attrName).incrementAndGet();
765                    break baseDNLoop;
766                  }
767                }
768              }
769            }
770          }
771        }
772        finally
773        {
774          final long count = entriesExamined.incrementAndGet();
775          if ((count % 1000L) == 0L)
776          {
777            out(count, " entries examined");
778          }
779        }
780      }
781    
782    
783    
784      /**
785       * Indicates that the provided search result reference has been returned by
786       * the server and may be processed by this search result listener.
787       *
788       * @param  searchReference  The search result reference that has been returned
789       *                          by the server.
790       */
791      public void searchReferenceReturned(
792                       final SearchResultReference searchReference)
793      {
794        // No implementation is required.  This tool will not follow referrals.
795      }
796    }