001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
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 */
021package com.unboundid.ldap.sdk.schema;
022
023
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.TreeMap;
035import java.util.concurrent.ConcurrentHashMap;
036import java.util.concurrent.atomic.AtomicLong;
037import java.util.concurrent.atomic.AtomicReference;
038import java.util.regex.Pattern;
039
040import com.unboundid.asn1.ASN1OctetString;
041import com.unboundid.ldap.matchingrules.MatchingRule;
042import com.unboundid.ldap.sdk.Attribute;
043import com.unboundid.ldap.sdk.Entry;
044import com.unboundid.ldap.sdk.LDAPException;
045import com.unboundid.ldap.sdk.RDN;
046import com.unboundid.util.Debug;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
053
054
055
056/**
057 * This class provides a mechanism for validating entries against a schema.  It
058 * provides the ability to customize the types of validation to perform, and can
059 * collect information about the entries that fail validation to provide a
060 * summary of the problems encountered.
061 * <BR><BR>
062 * The types of validation that may be performed for each entry include:
063 * <UL>
064 *   <LI>Ensure that the entry has a valid DN.</LI>
065 *   <LI>Ensure that all attribute values used in the entry's RDN are also
066 *       present in the entry.</LI>
067 *   <LI>Ensure that the entry has exactly one structural object class.</LI>
068 *   <LI>Ensure that all of the object classes for the entry are defined in the
069 *       schema.</LI>
070 *   <LI>Ensure that all of the auxiliary classes for the entry are allowed by
071 *       the DIT content rule for the entry's structural object class (if such a
072 *        DIT content rule is defined).</LI>
073 *   <LI>Ensure that all attributes contained in the entry are defined in the
074 *       schema.</LI>
075 *   <LI>Ensure that all attributes required by the entry's object classes or
076 *       DIT content rule (if defined) are present in the entry.</LI>
077 *   <LI>Ensure that all of the user attributes contained in the entry are
078 *       allowed by the entry's object classes or DIT content rule (if
079 *       defined).</LI>
080 *   <LI>Ensure that all attribute values conform to the requirements of the
081 *       associated attribute syntax.</LI>
082 *   <LI>Ensure that all attributes with multiple values are defined as
083 *       multi-valued in the associated schema.</LI>
084 *   <LI>If there is a name form associated with the entry's structural object
085 *       class, then ensure that the entry's RDN satisfies its constraints.</LI>
086 * </UL>
087 * All of these forms of validation will be performed by default, but individual
088 * types of validation may be enabled or disabled.
089 * <BR><BR>
090 * This class will not make any attempt to validate compliance with DIT
091 * structure rules, nor will it check the OBSOLETE field for any of the schema
092 * elements.  In addition, attempts to validate whether attribute values
093 * conform to the syntax for the associated attribute type may only be
094 * completely accurate for syntaxes supported by the LDAP SDK.
095 * <BR><BR>
096 * This class is largely threadsafe, and the {@link EntryValidator#entryIsValid}
097 * is designed so that it can be invoked concurrently by multiple threads.
098 * Note, however, that it is not recommended that the any of the other methods
099 * in this class be used while any threads are running the {@code entryIsValid}
100 * method because changing the configuration or attempting to retrieve retrieve
101 * information may yield inaccurate or inconsistent results.
102 */
103@ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE)
104public final class EntryValidator
105       implements Serializable
106{
107  /**
108   * The serial version UID for this serializable class.
109   */
110  private static final long serialVersionUID = -8945609557086398241L;
111
112
113
114  // A count of the total number of entries examined.
115  private final AtomicLong entriesExamined;
116
117  // A count of the number of entries missing an attribute value contained in
118  // the RDN.
119  private final AtomicLong entriesMissingRDNValues;
120
121  // A count of the total number of invalid entries encountered.
122  private final AtomicLong invalidEntries;
123
124  // A count of the number of entries with DNs that could not be parsed.
125  private final AtomicLong malformedDNs;
126
127  // A count of the number of entries missing a superior object class.
128  private final AtomicLong missingSuperiorClasses;
129
130  // A count of the number of entries containing multiple structural object
131  // classes.
132  private final AtomicLong multipleStructuralClasses;
133
134  // A count of the number of entries with RDNs that violate the associated
135  // name form.
136  private final AtomicLong nameFormViolations;
137
138  // A count of the number of entries without any object class.
139  private final AtomicLong noObjectClasses;
140
141  // A count of the number of entries without a structural object class.
142  private final AtomicLong noStructuralClass;
143
144  // Indicates whether an entry should be considered invalid if it contains an
145  // attribute value which violates the associated attribute syntax.
146  private boolean checkAttributeSyntax;
147
148  // Indicates whether an entry should be considered invalid if it contains one
149  // or more attribute values in its RDN that are not present in the set of
150  // entry attributes.
151  private boolean checkEntryMissingRDNValues;
152
153  // Indicates whether an entry should be considered invalid if its DN cannot be
154  // parsed.
155  private boolean checkMalformedDNs;
156
157  // Indicates whether an entry should be considered invalid if it is missing
158  // attributes required by its object classes or DIT content rule.
159  private boolean checkMissingAttributes;
160
161  // Indicates whether an entry should be considered invalid if it is missing
162  // one or more superior object classes.
163  private boolean checkMissingSuperiorObjectClasses;
164
165  // Indicates whether an entry should be considered invalid if its RDN does not
166  // conform to name form requirements.
167  private boolean checkNameForms;
168
169  // Indicates whether an entry should be considered invalid if it contains any
170  // attributes which are not allowed by its object classes or DIT content rule.
171  private boolean checkProhibitedAttributes;
172
173  // Indicates whether an entry should be considered invalid if it contains an
174  // auxiliary class that is not allowed by its DIT content rule or an abstract
175  // class that is not associated with a non-abstract class.
176  private boolean checkProhibitedObjectClasses;
177
178  // Indicates whether an entry should be considered invalid if it contains any
179  // attribute defined as single-valued with more than one values.
180  private boolean checkSingleValuedAttributes;
181
182  // Indicates whether an entry should be considered invalid if it does not
183  // contain exactly one structural object class.
184  private boolean checkStructuralObjectClasses;
185
186  // Indicates whether an entry should be considered invalid if it contains an
187  // attribute which is not defined in the schema.
188  private boolean checkUndefinedAttributes;
189
190  // Indicates whether an entry should be considered invalid if it contains an
191  // object class which is not defined in the schema.
192  private boolean checkUndefinedObjectClasses;
193
194  // A map of the attributes with values violating the associated syntax to the
195  // number of values found violating the syntax.
196  private final ConcurrentHashMap<String,AtomicLong> attributesViolatingSyntax;
197
198  // A map of the required attribute types that were missing from entries to
199  // the number of entries missing them.
200  private final ConcurrentHashMap<String,AtomicLong> missingAttributes;
201
202  // A map of the prohibited attribute types that were included in entries to
203  // the number of entries referencing them.
204  private final ConcurrentHashMap<String,AtomicLong> prohibitedAttributes;
205
206  // A map of the prohibited auxiliary object classes that were included in
207  // entries to the number of entries referencing them.
208  private final ConcurrentHashMap<String,AtomicLong> prohibitedObjectClasses;
209
210  // A map of the single-valued attributes with multiple values to the number
211  // of entries with multiple values for those attributes.
212  private final ConcurrentHashMap<String,AtomicLong> singleValueViolations;
213
214  // A map of undefined attribute types to the number of entries referencing
215  // them.
216  private final ConcurrentHashMap<String,AtomicLong> undefinedAttributes;
217
218  // A map of undefined object classes to the number of entries referencing
219  // them.
220  private final ConcurrentHashMap<String,AtomicLong> undefinedObjectClasses;
221
222  // The schema against which entries will be validated.
223  private final Schema schema;
224
225  // The attribute types for which to ignore syntax violations.
226  private Set<AttributeTypeDefinition> ignoreSyntaxViolationTypes;
227
228
229
230  /**
231   * Creates a new entry validator that will validate entries according to the
232   * provided schema.
233   *
234   * @param  schema  The schema against which entries will be validated.
235   */
236  public EntryValidator(final Schema schema)
237  {
238    this.schema = schema;
239
240    checkAttributeSyntax              = true;
241    checkEntryMissingRDNValues        = true;
242    checkMalformedDNs                 = true;
243    checkMissingAttributes            = true;
244    checkMissingSuperiorObjectClasses = true;
245    checkNameForms                    = true;
246    checkProhibitedAttributes         = true;
247    checkProhibitedObjectClasses      = true;
248    checkSingleValuedAttributes       = true;
249    checkStructuralObjectClasses      = true;
250    checkUndefinedAttributes          = true;
251    checkUndefinedObjectClasses       = true;
252
253    ignoreSyntaxViolationTypes = Collections.emptySet();
254
255    entriesExamined           = new AtomicLong(0L);
256    entriesMissingRDNValues   = new AtomicLong(0L);
257    invalidEntries            = new AtomicLong(0L);
258    malformedDNs              = new AtomicLong(0L);
259    missingSuperiorClasses    = new AtomicLong(0L);
260    multipleStructuralClasses = new AtomicLong(0L);
261    nameFormViolations        = new AtomicLong(0L);
262    noObjectClasses           = new AtomicLong(0L);
263    noStructuralClass         = new AtomicLong(0L);
264
265    attributesViolatingSyntax = new ConcurrentHashMap<>(20);
266    missingAttributes         = new ConcurrentHashMap<>(20);
267    prohibitedAttributes      = new ConcurrentHashMap<>(20);
268    prohibitedObjectClasses   = new ConcurrentHashMap<>(20);
269    singleValueViolations     = new ConcurrentHashMap<>(20);
270    undefinedAttributes       = new ConcurrentHashMap<>(20);
271    undefinedObjectClasses    = new ConcurrentHashMap<>(20);
272  }
273
274
275
276  /**
277   * Indicates whether the entry validator should consider entries invalid if
278   * they are missing attributes which are required by the object classes or
279   * DIT content rule (if applicable) for the entry.
280   *
281   * @return  {@code true} if entries that are missing attributes required by
282   *          its object classes or DIT content rule should be considered
283   *          invalid, or {@code false} if not.
284   */
285  public boolean checkMissingAttributes()
286  {
287    return checkMissingAttributes;
288  }
289
290
291
292  /**
293   * Specifies whether the entry validator should consider entries invalid if
294   * they are missing attributes which are required by the object classes or DIT
295   * content rule (if applicable) for the entry.
296   *
297   * @param  checkMissingAttributes  Indicates whether the entry validator
298   *                                 should consider entries invalid if they are
299   *                                 missing required attributes.
300   */
301  public void setCheckMissingAttributes(final boolean checkMissingAttributes)
302  {
303    this.checkMissingAttributes = checkMissingAttributes;
304  }
305
306
307
308  /**
309   * Indicates whether the entry validator should consider entries invalid if
310   * they are missing any superior classes for the included set of object
311   * classes.
312   *
313   * @return  {@code true} if entries that are missing superior classes should
314   *          be considered invalid, or {@code false} if not.
315   */
316  public boolean checkMissingSuperiorObjectClasses()
317  {
318    return checkMissingSuperiorObjectClasses;
319  }
320
321
322
323  /**
324   * Specifies whether the entry validator should consider entries invalid if
325   * they are missing any superior classes for the included set of object
326   * classes.
327   *
328   * @param  checkMissingSuperiorObjectClasses  Indicates whether the entry
329   *                                            validator should consider
330   *                                            entries invalid if they are
331   *                                            missing any superior classes for
332   *                                            the included set of object
333   *                                            classes.
334   */
335  public void setCheckMissingSuperiorObjectClasses(
336                   final boolean checkMissingSuperiorObjectClasses)
337  {
338    this.checkMissingSuperiorObjectClasses = checkMissingSuperiorObjectClasses;
339  }
340
341
342
343  /**
344   * Indicates whether the entry validator should consider entries invalid if
345   * their DNs cannot be parsed.
346   *
347   * @return  {@code true} if entries with malformed DNs should be considered
348   *          invalid, or {@code false} if not.
349   */
350  public boolean checkMalformedDNs()
351  {
352    return checkMalformedDNs;
353  }
354
355
356
357  /**
358   * Specifies whether the entry validator should consider entries invalid if
359   * their DNs cannot be parsed.
360   *
361   * @param  checkMalformedDNs  Specifies whether entries with malformed DNs
362   *                            should be considered invalid.
363   */
364  public void setCheckMalformedDNs(final boolean checkMalformedDNs)
365  {
366    this.checkMalformedDNs = checkMalformedDNs;
367  }
368
369
370
371  /**
372   * Indicates whether the entry validator should consider entries invalid if
373   * they contain one or more attribute values in their RDN that are not present
374   * in the set of entry attributes.
375   *
376   * @return  {@code true} if entries missing one or more attribute values
377   *          included in their RDNs should be considered invalid, or
378   *          {@code false} if not.
379   */
380  public boolean checkEntryMissingRDNValues()
381  {
382    return checkEntryMissingRDNValues;
383  }
384
385
386
387  /**
388   * Specifies whether the entry validator should consider entries invalid if
389   * they contain one or more attribute values in their RDN that are not present
390   * in the set of entry attributes.
391   *
392   * @param  checkEntryMissingRDNValues  Indicates whether the entry validator
393   *                                     should consider entries invalid if they
394   *                                     contain one or more attribute values in
395   *                                     their RDN that are not present in the
396   *                                     set of entry attributes.
397   */
398  public void setCheckEntryMissingRDNValues(
399                   final boolean checkEntryMissingRDNValues)
400  {
401    this.checkEntryMissingRDNValues = checkEntryMissingRDNValues;
402  }
403
404
405
406  /**
407   * Indicates whether the entry validator should consider entries invalid if
408   * the attributes contained in the RDN violate the constraints of the
409   * associated name form.
410   *
411   * @return  {@code true} if entries with RDNs that do not conform to the
412   *          associated name form should be considered invalid, or
413   *          {@code false} if not.
414   */
415  public boolean checkNameForms()
416  {
417    return checkNameForms;
418  }
419
420
421
422  /**
423   * Specifies whether the entry validator should consider entries invalid if
424   * the attributes contained in the RDN violate the constraints of the
425   * associated name form.
426   *
427   * @param  checkNameForms  Indicates whether the entry validator should
428   *                         consider entries invalid if their RDNs violate name
429   *                         form constraints.
430   */
431  public void setCheckNameForms(final boolean checkNameForms)
432  {
433    this.checkNameForms = checkNameForms;
434  }
435
436
437
438  /**
439   * Indicates whether the entry validator should consider entries invalid if
440   * they contain attributes which are not allowed by (or are prohibited by) the
441   * object classes and DIT content rule (if applicable) for the entry.
442   *
443   * @return  {@code true} if entries should be considered invalid if they
444   *          contain attributes which are not allowed, or {@code false} if not.
445   */
446  public boolean checkProhibitedAttributes()
447  {
448    return checkProhibitedAttributes;
449  }
450
451
452
453  /**
454   * Specifies whether the entry validator should consider entries invalid if
455   * they contain attributes which are not allowed by (or are prohibited by) the
456   * object classes and DIT content rule (if applicable) for the entry.
457   *
458   * @param  checkProhibitedAttributes  Indicates whether entries should be
459   *                                    considered invalid if they contain
460   *                                    attributes which are not allowed.
461   */
462  public void setCheckProhibitedAttributes(
463                   final boolean checkProhibitedAttributes)
464  {
465    this.checkProhibitedAttributes = checkProhibitedAttributes;
466  }
467
468
469
470  /**
471   * Indicates whether the entry validator should consider entries invalid if
472   * they contain auxiliary object classes which are not allowed by the DIT
473   * content rule (if applicable) for the entry, or if they contain any abstract
474   * object classes which are not subclassed by any non-abstract classes
475   * included in the entry.
476   *
477   * @return  {@code true} if entries should be considered invalid if they
478   *          contain prohibited object classes, or {@code false} if not.
479   */
480  public boolean checkProhibitedObjectClasses()
481  {
482    return checkProhibitedObjectClasses;
483  }
484
485
486
487  /**
488   * Specifies whether the entry validator should consider entries invalid if
489   * they contain auxiliary object classes which are not allowed by the DIT
490   * content rule (if applicable) for the entry, or if they contain any abstract
491   * object classes which are not subclassed by any non-abstract classes
492   * included in the entry.
493   *
494   * @param  checkProhibitedObjectClasses  Indicates whether entries should be
495   *                                       considered invalid if they contain
496   *                                       prohibited object classes.
497   */
498  public void setCheckProhibitedObjectClasses(
499                   final boolean checkProhibitedObjectClasses)
500  {
501    this.checkProhibitedObjectClasses = checkProhibitedObjectClasses;
502  }
503
504
505
506  /**
507   * Indicates whether the entry validator should consider entries invalid if
508   * they they contain attributes with more than one value which are declared as
509   * single-valued in the schema.
510   *
511   * @return  {@code true} if entries should be considered invalid if they
512   *          contain single-valued attributes with more than one value, or
513   *          {@code false} if not.
514   */
515  public boolean checkSingleValuedAttributes()
516  {
517    return checkSingleValuedAttributes;
518  }
519
520
521
522  /**
523   * Specifies whether the entry validator should consider entries invalid if
524   * they contain attributes with more than one value which are declared as
525   * single-valued in the schema.
526   *
527   * @param  checkSingleValuedAttributes  Indicates whether entries should be
528   *                                      considered invalid if they contain
529   *                                      single-valued attributes with more
530   *                                      than one value.
531   */
532  public void setCheckSingleValuedAttributes(
533                   final boolean checkSingleValuedAttributes)
534  {
535    this.checkSingleValuedAttributes = checkSingleValuedAttributes;
536  }
537
538
539
540  /**
541   * Indicates whether the entry validator should consider entries invalid if
542   * they do not contain exactly one structural object class (i.e., either do
543   * not have any structural object class, or have more than one).
544   *
545   * @return  {@code true} if entries should be considered invalid if they do
546   *          not have exactly one structural object class, or {@code false} if
547   *          not.
548   */
549  public boolean checkStructuralObjectClasses()
550  {
551    return checkStructuralObjectClasses;
552  }
553
554
555
556  /**
557   * Specifies whether the entry validator should consider entries invalid if
558   * they do not contain exactly one structural object class (i.e., either do
559   * not have any structural object class, or have more than one).
560   *
561   * @param  checkStructuralObjectClasses  Indicates whether entries should be
562   *                                       considered invalid if they do not
563   *                                       have exactly one structural object
564   *                                       class.
565   */
566  public void setCheckStructuralObjectClasses(
567                   final boolean checkStructuralObjectClasses)
568  {
569    this.checkStructuralObjectClasses = checkStructuralObjectClasses;
570  }
571
572
573
574  /**
575   * Indicates whether the entry validator should consider entries invalid if
576   * they contain attributes which violate the associated attribute syntax.
577   *
578   * @return  {@code true} if entries should be considered invalid if they
579   *          contain attribute values which violate the associated attribute
580   *          syntax, or {@code false} if not.
581   */
582  public boolean checkAttributeSyntax()
583  {
584    return checkAttributeSyntax;
585  }
586
587
588
589  /**
590   * Specifies whether the entry validator should consider entries invalid if
591   * they contain attributes which violate the associated attribute syntax.
592   *
593   * @param  checkAttributeSyntax  Indicates whether entries should be
594   *                               considered invalid if they violate the
595   *                               associated attribute syntax.
596   */
597  public void setCheckAttributeSyntax(final boolean checkAttributeSyntax)
598  {
599    this.checkAttributeSyntax = checkAttributeSyntax;
600  }
601
602
603
604  /**
605   * Retrieves the set of attribute types for which syntax violations should be
606   * ignored.  If {@link #checkAttributeSyntax()} returns {@code true}, then
607   * any attribute syntax violations will be flagged for all attributes except
608   * those attributes in this set.  If {@code checkAttributeSyntax()} returns
609   * {@code false}, then all syntax violations will be ignored.
610   *
611   * @return  The set of attribute types for which syntax violations should be
612   *          ignored.
613   */
614  public Set<AttributeTypeDefinition> getIgnoreSyntaxViolationsAttributeTypes()
615  {
616    return ignoreSyntaxViolationTypes;
617  }
618
619
620
621  /**
622   * Specifies the set of attribute types for which syntax violations should be
623   * ignored.  This method will only have any effect if
624   * {@link #checkAttributeSyntax()} returns {@code true}.
625   *
626   * @param  attributeTypes  The definitions for the attribute types for  which
627   *                         to ignore syntax violations.  It may be
628   *                         {@code null} or empty if no violations should be
629   *                         ignored.
630   */
631  public void setIgnoreSyntaxViolationAttributeTypes(
632                   final AttributeTypeDefinition... attributeTypes)
633  {
634    if (attributeTypes == null)
635    {
636      ignoreSyntaxViolationTypes = Collections.emptySet();
637    }
638    else
639    {
640      ignoreSyntaxViolationTypes = Collections.unmodifiableSet(
641           new HashSet<>(StaticUtils.toList(attributeTypes)));
642    }
643  }
644
645
646
647  /**
648   * Specifies the names or OIDs of the attribute types for which syntax
649   * violations should be ignored.  This method will only have any effect if
650   * {@link #checkAttributeSyntax()} returns {@code true}.
651   *
652   * @param  attributeTypes  The names or OIDs of the attribute types for  which
653   *                         to ignore syntax violations.  It may be
654   *                         {@code null} or empty if no violations should be
655   *                         ignored.
656   */
657  public void setIgnoreSyntaxViolationAttributeTypes(
658                   final String... attributeTypes)
659  {
660    setIgnoreSyntaxViolationAttributeTypes(StaticUtils.toList(attributeTypes));
661  }
662
663
664
665  /**
666   * Specifies the names or OIDs of the attribute types for which syntax
667   * violations should be ignored.  This method will only have any effect if
668   * {@link #checkAttributeSyntax()} returns {@code true}.
669   *
670   * @param  attributeTypes  The names or OIDs of the attribute types for  which
671   *                         to ignore syntax violations.  It may be
672   *                         {@code null} or empty if no violations should be
673   *                         ignored.  Any attribute types not defined in the
674   *                         schema will be ignored.
675   */
676  public void setIgnoreSyntaxViolationAttributeTypes(
677                   final Collection<String> attributeTypes)
678  {
679    if (attributeTypes == null)
680    {
681      ignoreSyntaxViolationTypes = Collections.emptySet();
682      return;
683    }
684
685    final HashSet<AttributeTypeDefinition> atSet =
686         new HashSet<>(attributeTypes.size());
687    for (final String s : attributeTypes)
688    {
689      final AttributeTypeDefinition d = schema.getAttributeType(s);
690      if (d != null)
691      {
692        atSet.add(d);
693      }
694    }
695
696    ignoreSyntaxViolationTypes = Collections.unmodifiableSet(atSet);
697  }
698
699
700
701  /**
702   * Indicates whether the entry validator should consider entries invalid if
703   * they contain attributes which are not defined in the schema.
704   *
705   * @return  {@code true} if entries should be considered invalid if they
706   *          contain attributes which are not defined in the schema, or
707   *          {@code false} if not.
708   */
709  public boolean checkUndefinedAttributes()
710  {
711    return checkUndefinedAttributes;
712  }
713
714
715
716  /**
717   * Specifies whether the entry validator should consider entries invalid if
718   * they contain attributes which are not defined in the schema.
719   *
720   * @param  checkUndefinedAttributes  Indicates whether entries should be
721   *                                   considered invalid if they contain
722   *                                   attributes which are not defined in the
723   *                                   schema, or {@code false} if not.
724   */
725  public void setCheckUndefinedAttributes(
726                   final boolean checkUndefinedAttributes)
727  {
728    this.checkUndefinedAttributes = checkUndefinedAttributes;
729  }
730
731
732
733  /**
734   * Indicates whether the entry validator should consider entries invalid if
735   * they contain object classes which are not defined in the schema.
736   *
737   * @return  {@code true} if entries should be considered invalid if they
738   *          contain object classes which are not defined in the schema, or
739   *          {@code false} if not.
740   */
741  public boolean checkUndefinedObjectClasses()
742  {
743    return checkUndefinedObjectClasses;
744  }
745
746
747
748  /**
749   * Specifies whether the entry validator should consider entries invalid if
750   * they contain object classes which are not defined in the schema.
751   *
752   * @param  checkUndefinedObjectClasses  Indicates whether entries should be
753   *                                      considered invalid if they contain
754   *                                      object classes which are not defined
755   *                                      in the schema.
756   */
757  public void setCheckUndefinedObjectClasses(
758                   final boolean checkUndefinedObjectClasses)
759  {
760    this.checkUndefinedObjectClasses = checkUndefinedObjectClasses;
761  }
762
763
764
765  /**
766   * Indicates whether the provided entry passes all of the enabled types of
767   * validation.
768   *
769   * @param  entry           The entry to be examined.   It must not be
770   *                         {@code null}.
771   * @param  invalidReasons  A list to which messages may be added which provide
772   *                         information about why the entry is invalid.  It may
773   *                         be {@code null} if this information is not needed.
774   *
775   * @return  {@code true} if the entry conforms to all of the enabled forms of
776   *          validation, or {@code false} if the entry fails at least one of
777   *          the tests.
778   */
779  public boolean entryIsValid(final Entry entry,
780                              final List<String> invalidReasons)
781  {
782    Validator.ensureNotNull(entry);
783
784    boolean entryValid = true;
785    entriesExamined.incrementAndGet();
786
787    // Get the parsed DN for the entry.
788    RDN rdn = null;
789    try
790    {
791      rdn = entry.getParsedDN().getRDN();
792    }
793    catch (final LDAPException le)
794    {
795      Debug.debugException(le);
796      if (checkMalformedDNs)
797      {
798        entryValid = false;
799        malformedDNs.incrementAndGet();
800        if (invalidReasons != null)
801        {
802          invalidReasons.add(ERR_ENTRY_MALFORMED_DN.get(
803               StaticUtils.getExceptionMessage(le)));
804        }
805      }
806    }
807
808    // Get the object class descriptions for the object classes in the entry.
809    final HashSet<ObjectClassDefinition> ocSet = new HashSet<>(10);
810    final boolean missingOC =
811         (! getObjectClasses(entry, ocSet, invalidReasons));
812    if (missingOC)
813    {
814      entryValid = false;
815    }
816
817    // If the entry was not missing any object classes, then get the structural
818    // class for the entry and use it to get the associated DIT content rule and
819    // name form.
820    DITContentRuleDefinition ditContentRule = null;
821    NameFormDefinition nameForm = null;
822    if (! missingOC)
823    {
824      final AtomicReference<ObjectClassDefinition> ref =
825           new AtomicReference<>(null);
826      entryValid &= getStructuralClass(ocSet, ref, invalidReasons);
827      final ObjectClassDefinition structuralClass = ref.get();
828      if (structuralClass != null)
829      {
830        ditContentRule = schema.getDITContentRule(structuralClass.getOID());
831        nameForm =
832             schema.getNameFormByObjectClass(structuralClass.getNameOrOID());
833      }
834    }
835
836    // If we should check for missing required attributes, then do so.
837    HashSet<AttributeTypeDefinition> requiredAttrs = null;
838    if (checkMissingAttributes || checkProhibitedAttributes)
839    {
840      requiredAttrs = getRequiredAttributes(ocSet, ditContentRule);
841      if (checkMissingAttributes)
842      {
843        entryValid &= checkForMissingAttributes(entry, rdn, requiredAttrs,
844                                                invalidReasons);
845      }
846    }
847
848    // Iterate through all of the attributes in the entry.  Make sure that they
849    // are all defined in the schema, that they are allowed to be present in the
850    // entry, that their values conform to the associated syntax, and that any
851    // single-valued attributes have only one value.
852    HashSet<AttributeTypeDefinition> optionalAttrs = null;
853    if (checkProhibitedAttributes)
854    {
855      optionalAttrs =
856           getOptionalAttributes(ocSet, ditContentRule, requiredAttrs);
857    }
858    for (final Attribute a : entry.getAttributes())
859    {
860      entryValid &=
861           checkAttribute(a, requiredAttrs, optionalAttrs, invalidReasons);
862    }
863
864    // If there is a DIT content rule, then check to ensure that all of the
865    // auxiliary object classes are allowed.
866    if (checkProhibitedObjectClasses && (ditContentRule != null))
867    {
868      entryValid &=
869           checkAuxiliaryClasses(ocSet, ditContentRule, invalidReasons);
870    }
871
872    // Check the entry's RDN to ensure that all attributes are defined in the
873    // schema, allowed to be present, and comply with the name form.
874    if (rdn != null)
875    {
876      entryValid &= checkRDN(rdn, entry, requiredAttrs, optionalAttrs, nameForm,
877                             invalidReasons);
878    }
879
880    if (! entryValid)
881    {
882      invalidEntries.incrementAndGet();
883    }
884
885    return entryValid;
886  }
887
888
889
890  /**
891   * Gets the object classes for the entry, including any that weren't
892   * explicitly included but should be because they were superior to classes
893   * that were included.
894   *
895   * @param  entry           The entry to examine.
896   * @param  ocSet           The set into which the object class definitions
897   *                         should be placed.
898   * @param  invalidReasons  A list to which messages may be added which provide
899   *                         information about why the entry is invalid.  It may
900   *                         be {@code null} if this information is not needed.
901   *
902   * @return  {@code true} if the entry passed all validation processing
903   *          performed by this method, or {@code false} if there were any
904   *          failures.
905   */
906  private boolean getObjectClasses(final Entry entry,
907                                   final HashSet<ObjectClassDefinition> ocSet,
908                                   final List<String> invalidReasons)
909  {
910    final String[] ocValues = entry.getObjectClassValues();
911    if ((ocValues == null) || (ocValues.length == 0))
912    {
913      noObjectClasses.incrementAndGet();
914      if (invalidReasons != null)
915      {
916        invalidReasons.add(ERR_ENTRY_NO_OCS.get());
917      }
918      return false;
919    }
920
921    boolean entryValid = true;
922    final HashSet<String> missingOCs = new HashSet<>(ocValues.length);
923    for (final String ocName : entry.getObjectClassValues())
924    {
925      final ObjectClassDefinition d = schema.getObjectClass(ocName);
926      if (d == null)
927      {
928        if (checkUndefinedObjectClasses)
929        {
930          entryValid = false;
931          missingOCs.add(StaticUtils.toLowerCase(ocName));
932          updateCount(ocName, undefinedObjectClasses);
933          if (invalidReasons != null)
934          {
935            invalidReasons.add(ERR_ENTRY_UNDEFINED_OC.get(ocName));
936          }
937        }
938      }
939      else
940      {
941        ocSet.add(d);
942      }
943    }
944
945    for (final ObjectClassDefinition d : new HashSet<>(ocSet))
946    {
947      entryValid &= addSuperiorClasses(d, ocSet, missingOCs, invalidReasons);
948    }
949
950    return entryValid;
951  }
952
953
954
955  /**
956   * Recursively adds the definition superior class for the provided object
957   * class definition to the provided set, if it is not already present.
958   *
959   * @param  d               The object class definition to process.
960   * @param  ocSet           The set into which the object class definitions
961   *                         should be placed.
962   * @param  missingOCNames  The names of the object classes we already know are
963   *                         missing and therefore shouldn't be flagged again.
964   * @param  invalidReasons  A list to which messages may be added which provide
965   *                         information about why the entry is invalid.  It may
966   *                         be {@code null} if this information is not needed.
967   *
968   * @return  {@code true} if the entry passed all validation processing
969   *          performed by this method, or {@code false} if there were any
970   *          failures.
971   */
972  private boolean addSuperiorClasses(final ObjectClassDefinition d,
973                                     final HashSet<ObjectClassDefinition> ocSet,
974                                     final HashSet<String> missingOCNames,
975                                     final List<String> invalidReasons)
976  {
977    boolean entryValid = true;
978
979    for (final String ocName : d.getSuperiorClasses())
980    {
981      final ObjectClassDefinition supOC = schema.getObjectClass(ocName);
982      if (supOC == null)
983      {
984        if (checkUndefinedObjectClasses)
985        {
986          entryValid = false;
987          final String lowerName = StaticUtils.toLowerCase(ocName);
988          if (! missingOCNames.contains(lowerName))
989          {
990            missingOCNames.add(lowerName);
991            updateCount(ocName, undefinedObjectClasses);
992            if (invalidReasons != null)
993            {
994              invalidReasons.add(ERR_ENTRY_UNDEFINED_SUP_OC.get(
995                   d.getNameOrOID(), ocName));
996            }
997          }
998        }
999      }
1000      else
1001      {
1002        if (! ocSet.contains(supOC))
1003        {
1004          ocSet.add(supOC);
1005          if (checkMissingSuperiorObjectClasses)
1006          {
1007            entryValid = false;
1008            missingSuperiorClasses.incrementAndGet();
1009            if (invalidReasons != null)
1010            {
1011              invalidReasons.add(ERR_ENTRY_MISSING_SUP_OC.get(
1012                   supOC.getNameOrOID(), d.getNameOrOID()));
1013            }
1014          }
1015        }
1016
1017        entryValid &=
1018             addSuperiorClasses(supOC, ocSet, missingOCNames, invalidReasons);
1019      }
1020    }
1021
1022    return entryValid;
1023  }
1024
1025
1026
1027  /**
1028   * Retrieves the structural object class from the set of provided object
1029   * classes.
1030   *
1031   * @param  ocSet            The set of object class definitions for the entry.
1032   * @param  structuralClass  The reference that will be updated with the
1033   *                          entry's structural object class.
1034   * @param  invalidReasons   A list to which messages may be added which
1035   *                          provide provide information about why the entry is
1036   *                          invalid.  It may be {@code null} if this
1037   *                          information is not needed.
1038   *
1039   * @return  {@code true} if the entry passes all validation checks performed
1040   *          by this method, or {@code false} if not.
1041   */
1042  private boolean getStructuralClass(final HashSet<ObjectClassDefinition> ocSet,
1043               final AtomicReference<ObjectClassDefinition> structuralClass,
1044               final List<String> invalidReasons)
1045  {
1046    final HashSet<ObjectClassDefinition> ocCopy = new HashSet<>(ocSet);
1047    for (final ObjectClassDefinition d : ocSet)
1048    {
1049      final ObjectClassType t = d.getObjectClassType(schema);
1050      if (t == ObjectClassType.STRUCTURAL)
1051      {
1052        ocCopy.removeAll(d.getSuperiorClasses(schema, true));
1053      }
1054      else if (t == ObjectClassType.AUXILIARY)
1055      {
1056        ocCopy.remove(d);
1057        ocCopy.removeAll(d.getSuperiorClasses(schema, true));
1058      }
1059    }
1060
1061    // Iterate through the set of remaining classes and strip out any
1062    // abstract classes.
1063    boolean entryValid = true;
1064    Iterator<ObjectClassDefinition> iterator = ocCopy.iterator();
1065    while (iterator.hasNext())
1066    {
1067      final ObjectClassDefinition d = iterator.next();
1068      if (d.getObjectClassType(schema) == ObjectClassType.ABSTRACT)
1069      {
1070        if (checkProhibitedObjectClasses)
1071        {
1072          entryValid = false;
1073          updateCount(d.getNameOrOID(), prohibitedObjectClasses);
1074          if (invalidReasons != null)
1075          {
1076            invalidReasons.add(ERR_ENTRY_INVALID_ABSTRACT_CLASS.get(
1077                 d.getNameOrOID()));
1078          }
1079        }
1080        iterator.remove();
1081      }
1082    }
1083
1084    switch (ocCopy.size())
1085    {
1086      case 0:
1087        if (checkStructuralObjectClasses)
1088        {
1089          entryValid = false;
1090          noStructuralClass.incrementAndGet();
1091          if (invalidReasons != null)
1092          {
1093            invalidReasons.add(ERR_ENTRY_NO_STRUCTURAL_CLASS.get());
1094          }
1095        }
1096        break;
1097
1098      case 1:
1099        structuralClass.set(ocCopy.iterator().next());
1100        break;
1101
1102      default:
1103        if (checkStructuralObjectClasses)
1104        {
1105          entryValid = false;
1106          multipleStructuralClasses.incrementAndGet();
1107          if (invalidReasons != null)
1108          {
1109            final StringBuilder ocList = new StringBuilder();
1110            iterator = ocCopy.iterator();
1111            while (iterator.hasNext())
1112            {
1113              ocList.append(iterator.next().getNameOrOID());
1114              if (iterator.hasNext())
1115              {
1116                ocList.append(", ");
1117              }
1118            }
1119            invalidReasons.add(
1120                 ERR_ENTRY_MULTIPLE_STRUCTURAL_CLASSES.get(ocList));
1121          }
1122        }
1123        break;
1124    }
1125
1126    return entryValid;
1127  }
1128
1129
1130
1131  /**
1132   * Retrieves the set of attributes which must be present in entries with the
1133   * provided set of object classes and DIT content rule.
1134   *
1135   * @param  ocSet           The set of object classes for the entry.
1136   * @param  ditContentRule  The DIT content rule for the entry, if defined.
1137   *
1138   * @return  The set of attributes which must be present in entries with the
1139   *          provided set of object classes and DIT content rule.
1140   */
1141  private HashSet<AttributeTypeDefinition> getRequiredAttributes(
1142               final HashSet<ObjectClassDefinition> ocSet,
1143               final DITContentRuleDefinition ditContentRule)
1144  {
1145    final HashSet<AttributeTypeDefinition> attrSet = new HashSet<>(20);
1146    for (final ObjectClassDefinition oc : ocSet)
1147    {
1148      attrSet.addAll(oc.getRequiredAttributes(schema, false));
1149    }
1150
1151    if (ditContentRule != null)
1152    {
1153      for (final String s : ditContentRule.getRequiredAttributes())
1154      {
1155        final AttributeTypeDefinition d = schema.getAttributeType(s);
1156        if (d != null)
1157        {
1158          attrSet.add(d);
1159        }
1160      }
1161    }
1162
1163    return attrSet;
1164  }
1165
1166
1167
1168  /**
1169   * Retrieves the set of attributes which may optionally be present in entries
1170   * with the provided set of object classes and DIT content rule.
1171   *
1172   * @param  ocSet            The set of object classes for the entry.
1173   * @param  ditContentRule   The DIT content rule for the entry, if defined.
1174   * @param  requiredAttrSet  The set of required attributes for the entry.
1175   *
1176   * @return  The set of attributes which may optionally be present in entries
1177   *          with the provided set of object classes and DIT content rule.
1178   */
1179  private HashSet<AttributeTypeDefinition> getOptionalAttributes(
1180               final HashSet<ObjectClassDefinition> ocSet,
1181               final DITContentRuleDefinition ditContentRule,
1182               final HashSet<AttributeTypeDefinition> requiredAttrSet)
1183  {
1184    final HashSet<AttributeTypeDefinition> attrSet = new HashSet<>(20);
1185    for (final ObjectClassDefinition oc : ocSet)
1186    {
1187      if (oc.hasNameOrOID("extensibleObject") ||
1188          oc.hasNameOrOID("1.3.6.1.4.1.1466.101.120.111"))
1189      {
1190        attrSet.addAll(schema.getUserAttributeTypes());
1191        break;
1192      }
1193
1194      for (final AttributeTypeDefinition d :
1195           oc.getOptionalAttributes(schema, false))
1196      {
1197        if (! requiredAttrSet.contains(d))
1198        {
1199          attrSet.add(d);
1200        }
1201      }
1202    }
1203
1204    if (ditContentRule != null)
1205    {
1206      for (final String s : ditContentRule.getOptionalAttributes())
1207      {
1208        final AttributeTypeDefinition d = schema.getAttributeType(s);
1209        if ((d != null) && (! requiredAttrSet.contains(d)))
1210        {
1211          attrSet.add(d);
1212        }
1213      }
1214
1215      for (final String s : ditContentRule.getProhibitedAttributes())
1216      {
1217        final AttributeTypeDefinition d = schema.getAttributeType(s);
1218        if (d != null)
1219        {
1220          attrSet.remove(d);
1221        }
1222      }
1223    }
1224
1225    return attrSet;
1226  }
1227
1228
1229
1230  /**
1231   * Checks the provided entry to determine whether it is missing any required
1232   * attributes.
1233   *
1234   * @param  entry           The entry to examine.
1235   * @param  rdn             The RDN for the entry, if available.
1236   * @param  requiredAttrs   The set of attribute types which are required to be
1237   *                         included in the entry.
1238   * @param  invalidReasons  A list to which messages may be added which provide
1239   *                         information about why the entry is invalid.  It may
1240   *                         be {@code null} if this information is not needed.
1241   *
1242   * @return  {@code true} if the entry has all required attributes, or
1243   *          {@code false} if not.
1244   */
1245  private boolean checkForMissingAttributes(final Entry entry, final RDN rdn,
1246                       final HashSet<AttributeTypeDefinition> requiredAttrs,
1247                       final List<String> invalidReasons)
1248  {
1249    boolean entryValid = true;
1250
1251    for (final AttributeTypeDefinition d : requiredAttrs)
1252    {
1253      boolean found = false;
1254      for (final String s : d.getNames())
1255      {
1256        if (entry.hasAttribute(s) || ((rdn != null) && rdn.hasAttribute(s)))
1257        {
1258          found = true;
1259          break;
1260        }
1261      }
1262
1263      if (! found)
1264      {
1265        if (! (entry.hasAttribute(d.getOID()) ||
1266               ((rdn != null) && (rdn.hasAttribute(d.getOID())))))
1267        {
1268          entryValid = false;
1269          updateCount(d.getNameOrOID(), missingAttributes);
1270          if (invalidReasons != null)
1271          {
1272            invalidReasons.add(ERR_ENTRY_MISSING_REQUIRED_ATTR.get(
1273                 d.getNameOrOID()));
1274          }
1275        }
1276      }
1277    }
1278
1279    return entryValid;
1280  }
1281
1282
1283
1284  /**
1285   * Checks the provided attribute to determine whether it appears to be valid.
1286   *
1287   * @param  attr            The attribute to examine.
1288   * @param  requiredAttrs   The set of attribute types which are required to be
1289   *                         included in the entry.
1290   * @param  optionalAttrs   The set of attribute types which may optionally be
1291   *                         included in the entry.
1292   * @param  invalidReasons  A list to which messages may be added which provide
1293   *                         information about why the entry is invalid.  It may
1294   *                         be {@code null} if this information is not needed.
1295   *
1296   * @return  {@code true} if the attribute passed all of the checks and appears
1297   *          to be valid, or {@code false} if it failed any of the checks.
1298   */
1299  private boolean checkAttribute(final Attribute attr,
1300                       final HashSet<AttributeTypeDefinition> requiredAttrs,
1301                       final HashSet<AttributeTypeDefinition> optionalAttrs,
1302                       final List<String> invalidReasons)
1303  {
1304    boolean entryValid = true;
1305
1306    final AttributeTypeDefinition d =
1307         schema.getAttributeType(attr.getBaseName());
1308    if (d == null)
1309    {
1310      if (checkUndefinedAttributes)
1311      {
1312        entryValid = false;
1313        updateCount(attr.getBaseName(), undefinedAttributes);
1314        if (invalidReasons != null)
1315        {
1316          invalidReasons.add(ERR_ENTRY_UNDEFINED_ATTR.get(attr.getBaseName()));
1317        }
1318      }
1319
1320      return entryValid;
1321    }
1322
1323    if (checkProhibitedAttributes && (! d.isOperational()))
1324    {
1325      if (! (requiredAttrs.contains(d) || optionalAttrs.contains(d)))
1326      {
1327        entryValid = false;
1328        updateCount(d.getNameOrOID(), prohibitedAttributes);
1329        if (invalidReasons != null)
1330        {
1331          invalidReasons.add(ERR_ENTRY_ATTR_NOT_ALLOWED.get(d.getNameOrOID()));
1332        }
1333      }
1334    }
1335
1336    final ASN1OctetString[] rawValues = attr.getRawValues();
1337    if (checkSingleValuedAttributes && d.isSingleValued() &&
1338        (rawValues.length > 1))
1339    {
1340      entryValid = false;
1341      updateCount(d.getNameOrOID(), singleValueViolations);
1342      if (invalidReasons != null)
1343      {
1344        invalidReasons.add(
1345             ERR_ENTRY_ATTR_HAS_MULTIPLE_VALUES.get(d.getNameOrOID()));
1346      }
1347    }
1348
1349    if (checkAttributeSyntax)
1350    {
1351      if (! ignoreSyntaxViolationTypes.contains(d))
1352      {
1353        final MatchingRule r =
1354             MatchingRule.selectEqualityMatchingRule(d.getNameOrOID(), schema);
1355        final Map<String, String[]> extensions = d.getExtensions();
1356        for (final ASN1OctetString v : rawValues)
1357        {
1358          try
1359          {
1360            r.normalize(v);
1361          }
1362          catch (final LDAPException le)
1363          {
1364            Debug.debugException(le);
1365            entryValid = false;
1366            updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1367            if (invalidReasons != null)
1368            {
1369              invalidReasons.add(ERR_ENTRY_ATTR_INVALID_SYNTAX.get(
1370                   v.stringValue(), d.getNameOrOID(),
1371                   StaticUtils.getExceptionMessage(le)));
1372            }
1373          }
1374
1375
1376          // If the attribute type definition includes an X-ALLOWED-VALUE
1377          // extension, then make sure the value is in that set.
1378          final String[] allowedValues = extensions.get("X-ALLOWED-VALUE");
1379          if (allowedValues != null)
1380          {
1381            boolean isAllowed = false;
1382            for (final String allowedValue : allowedValues)
1383            {
1384              try
1385              {
1386                if (r.valuesMatch(v, new ASN1OctetString(allowedValue)))
1387                {
1388                  isAllowed = true;
1389                  break;
1390                }
1391              }
1392              catch (final Exception e)
1393              {
1394                Debug.debugException(e);
1395              }
1396            }
1397
1398            if (! isAllowed)
1399            {
1400              entryValid = false;
1401              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1402              if (invalidReasons != null)
1403              {
1404                invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_ALLOWED.get(
1405                     v.stringValue(), d.getNameOrOID()));
1406              }
1407            }
1408          }
1409
1410
1411          // If the attribute type definition includes an X-VALUE-REGEX
1412          // extension, then make sure the value matches one of those regexes.
1413          final String[] valueRegexes = extensions.get("X-VALUE-REGEX");
1414          if (valueRegexes != null)
1415          {
1416            boolean matchesRegex = false;
1417            for (final String regex : valueRegexes)
1418            {
1419              try
1420              {
1421                final Pattern pattern = Pattern.compile(regex);
1422                if (pattern.matcher(v.stringValue()).matches())
1423                {
1424                  matchesRegex = true;
1425                  break;
1426                }
1427              }
1428              catch (final Exception e)
1429              {
1430                Debug.debugException(e);
1431              }
1432            }
1433
1434            if (! matchesRegex)
1435            {
1436              entryValid = false;
1437              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1438              if (invalidReasons != null)
1439              {
1440                invalidReasons.add(
1441                     ERR_ENTRY_ATTR_VALUE_NOT_ALLOWED_BY_REGEX.get(
1442                          v.stringValue(), d.getNameOrOID()));
1443              }
1444            }
1445          }
1446
1447
1448          // If the attribute type definition includes an X-MIN-VALUE-LENGTH
1449          // extension, then make sure the value is long enough.
1450          final String[] minValueLengths = extensions.get("X-MIN-VALUE-LENGTH");
1451          if (minValueLengths != null)
1452          {
1453            int minLength = 0;
1454            for (final String s : minValueLengths)
1455            {
1456              try
1457              {
1458                minLength = Math.max(minLength, Integer.parseInt(s));
1459              }
1460              catch (final Exception e)
1461              {
1462                Debug.debugException(e);
1463              }
1464            }
1465
1466            if (v.stringValue().length() < minLength)
1467            {
1468              entryValid = false;
1469              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1470              if (invalidReasons != null)
1471              {
1472                invalidReasons.add(
1473                     ERR_ENTRY_ATTR_VALUE_SHORTER_THAN_MIN_LENGTH.get(
1474                          v.stringValue(), d.getNameOrOID(), minLength));
1475              }
1476            }
1477          }
1478
1479
1480          // If the attribute type definition includes an X-MAX-VALUE-LENGTH
1481          // extension, then make sure the value is short enough.
1482          final String[] maxValueLengths = extensions.get("X-MAX-VALUE-LENGTH");
1483          if (maxValueLengths != null)
1484          {
1485            int maxLength = Integer.MAX_VALUE;
1486            for (final String s : maxValueLengths)
1487            {
1488              try
1489              {
1490                maxLength = Math.min(maxLength, Integer.parseInt(s));
1491              }
1492              catch (final Exception e)
1493              {
1494                Debug.debugException(e);
1495              }
1496            }
1497
1498            if (v.stringValue().length() > maxLength)
1499            {
1500              entryValid = false;
1501              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1502              if (invalidReasons != null)
1503              {
1504                invalidReasons.add(
1505                     ERR_ENTRY_ATTR_VALUE_LONGER_THAN_MAX_LENGTH.get(
1506                          v.stringValue(), d.getNameOrOID(), maxLength));
1507              }
1508            }
1509          }
1510
1511
1512          // If the attribute type definition includes an X-MIN-INT-VALUE
1513          // extension, then make sure the value is large enough.
1514          final String[] minIntValues = extensions.get("X-MIN-INT-VALUE");
1515          if (minIntValues != null)
1516          {
1517            try
1518            {
1519              final long longValue = Long.parseLong(v.stringValue());
1520
1521              long minAllowedValue = 0L;
1522              for (final String s : minIntValues)
1523              {
1524                try
1525                {
1526                  minAllowedValue =
1527                       Math.max(minAllowedValue, Long.parseLong(s));
1528                }
1529                catch (final Exception e)
1530                {
1531                  Debug.debugException(e);
1532                }
1533              }
1534
1535              if (longValue < minAllowedValue)
1536              {
1537                entryValid = false;
1538                updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1539                if (invalidReasons != null)
1540                {
1541                  invalidReasons.add(ERR_ENTRY_ATTR_VALUE_INT_TOO_SMALL.get(
1542                       longValue, d.getNameOrOID(), minAllowedValue));
1543                }
1544              }
1545            }
1546            catch (final Exception e)
1547            {
1548              Debug.debugException(e);
1549              entryValid = false;
1550              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1551              if (invalidReasons != null)
1552              {
1553                invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_INT.get(
1554                     v.stringValue(), d.getNameOrOID(), "X-MIN-INT-VALUE"));
1555              }
1556            }
1557          }
1558
1559
1560          // If the attribute type definition includes an X-MAX-INT-VALUE
1561          // extension, then make sure the value is large enough.
1562          final String[] maxIntValues = extensions.get("X-MAX-INT-VALUE");
1563          if (maxIntValues != null)
1564          {
1565            try
1566            {
1567              final long longValue = Long.parseLong(v.stringValue());
1568
1569              long maxAllowedValue = Long.MAX_VALUE;
1570              for (final String s : maxIntValues)
1571              {
1572                try
1573                {
1574                  maxAllowedValue =
1575                       Math.min(maxAllowedValue, Long.parseLong(s));
1576                }
1577                catch (final Exception e)
1578                {
1579                  Debug.debugException(e);
1580                }
1581              }
1582
1583              if (longValue > maxAllowedValue)
1584              {
1585                entryValid = false;
1586                updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1587                if (invalidReasons != null)
1588                {
1589                  invalidReasons.add(ERR_ENTRY_ATTR_VALUE_INT_TOO_LARGE.get(
1590                       longValue, d.getNameOrOID(), maxAllowedValue));
1591                }
1592              }
1593            }
1594            catch (final Exception e)
1595            {
1596              Debug.debugException(e);
1597              entryValid = false;
1598              updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1599              if (invalidReasons != null)
1600              {
1601                invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_INT.get(
1602                     v.stringValue(), d.getNameOrOID(), "X-MAX-INT-VALUE"));
1603              }
1604            }
1605          }
1606        }
1607
1608
1609        // If the attribute type definition includes an X-MIN-VALUE-COUNT
1610        // extension, then make sure the value has enough values.
1611        final String[] minValueCounts = extensions.get("X-MIN-VALUE-COUNT");
1612        if (minValueCounts != null)
1613        {
1614          int minValueCount = 0;
1615          for (final String s : minValueCounts)
1616          {
1617            try
1618            {
1619              minValueCount = Math.max(minValueCount, Integer.parseInt(s));
1620            }
1621            catch (final Exception e)
1622            {
1623              Debug.debugException(e);
1624            }
1625          }
1626
1627          if (rawValues.length < minValueCount)
1628          {
1629            entryValid = false;
1630            updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1631            if (invalidReasons != null)
1632            {
1633              invalidReasons.add(ERR_ENTRY_TOO_FEW_VALUES.get(rawValues.length,
1634                   d.getNameOrOID(), minValueCount));
1635            }
1636          }
1637        }
1638
1639
1640        // If the attribute type definition includes an X-MAX-VALUE-COUNT
1641        // extension, then make sure the value has enough values.
1642        final String[] maxValueCounts = extensions.get("X-MAX-VALUE-COUNT");
1643        if (maxValueCounts != null)
1644        {
1645          int maxValueCount = Integer.MAX_VALUE;
1646          for (final String s : maxValueCounts)
1647          {
1648            try
1649            {
1650              maxValueCount = Math.min(maxValueCount, Integer.parseInt(s));
1651            }
1652            catch (final Exception e)
1653            {
1654              Debug.debugException(e);
1655            }
1656          }
1657
1658          if (rawValues.length > maxValueCount)
1659          {
1660            entryValid = false;
1661            updateCount(d.getNameOrOID(), attributesViolatingSyntax);
1662            if (invalidReasons != null)
1663            {
1664              invalidReasons.add(ERR_ENTRY_TOO_MANY_VALUES.get(rawValues.length,
1665                   d.getNameOrOID(), maxValueCount));
1666            }
1667          }
1668        }
1669      }
1670    }
1671
1672    return entryValid;
1673  }
1674
1675
1676
1677  /**
1678   * Ensures that all of the auxiliary object classes contained in the object
1679   * class set are allowed by the provided DIT content rule.
1680   *
1681   * @param  ocSet           The set of object classes contained in the entry.
1682   * @param  ditContentRule  The DIT content rule to use to make the
1683   *                         determination.
1684   * @param  invalidReasons  A list to which messages may be added which provide
1685   *                         information about why the entry is invalid.  It may
1686   *                         be {@code null} if this information is not needed.
1687   *
1688   * @return  {@code true} if the entry passes all checks performed by this
1689   *          method, or {@code false} if not.
1690   */
1691  private boolean checkAuxiliaryClasses(
1692                       final HashSet<ObjectClassDefinition> ocSet,
1693                       final DITContentRuleDefinition ditContentRule,
1694                       final List<String> invalidReasons)
1695  {
1696    final HashSet<ObjectClassDefinition> auxSet = new HashSet<>(20);
1697    for (final String s : ditContentRule.getAuxiliaryClasses())
1698    {
1699      final ObjectClassDefinition d = schema.getObjectClass(s);
1700      if (d != null)
1701      {
1702        auxSet.add(d);
1703      }
1704    }
1705
1706    boolean entryValid = true;
1707    for (final ObjectClassDefinition d : ocSet)
1708    {
1709      final ObjectClassType t = d.getObjectClassType(schema);
1710      if ((t == ObjectClassType.AUXILIARY) && (! auxSet.contains(d)))
1711      {
1712        entryValid = false;
1713        updateCount(d.getNameOrOID(), prohibitedObjectClasses);
1714        if (invalidReasons != null)
1715        {
1716          invalidReasons.add(
1717               ERR_ENTRY_AUX_CLASS_NOT_ALLOWED.get(d.getNameOrOID()));
1718        }
1719      }
1720    }
1721
1722    return entryValid;
1723  }
1724
1725
1726
1727  /**
1728   * Ensures that the provided RDN is acceptable.  It will ensure that all
1729   * attributes are defined in the schema and allowed for the entry, and that
1730   * the entry optionally conforms to the associated name form.
1731   *
1732   * @param  rdn             The RDN to examine.
1733   * @param  entry           The entry to examine.
1734   * @param  requiredAttrs   The set of attribute types which are required to be
1735   *                         included in the entry.
1736   * @param  optionalAttrs   The set of attribute types which may optionally be
1737   *                         included in the entry.
1738   * @param  nameForm        The name for to use to make the determination, if
1739   *                         defined.
1740   * @param  invalidReasons  A list to which messages may be added which provide
1741   *                         information about why the entry is invalid.  It may
1742   *                         be {@code null} if this information is not needed.
1743   *
1744   * @return  {@code true} if the entry passes all checks performed by this
1745   *          method, or {@code false} if not.
1746   */
1747  private boolean checkRDN(final RDN rdn, final Entry entry,
1748                           final HashSet<AttributeTypeDefinition> requiredAttrs,
1749                           final HashSet<AttributeTypeDefinition> optionalAttrs,
1750                           final NameFormDefinition nameForm,
1751                           final List<String> invalidReasons)
1752  {
1753    final HashSet<AttributeTypeDefinition> nfReqAttrs = new HashSet<>(5);
1754    final HashSet<AttributeTypeDefinition> nfAllowedAttrs = new HashSet<>(5);
1755    if (nameForm != null)
1756    {
1757      for (final String s : nameForm.getRequiredAttributes())
1758      {
1759        final AttributeTypeDefinition d = schema.getAttributeType(s);
1760        if (d != null)
1761        {
1762          nfReqAttrs.add(d);
1763        }
1764      }
1765
1766      nfAllowedAttrs.addAll(nfReqAttrs);
1767      for (final String s : nameForm.getOptionalAttributes())
1768      {
1769        final AttributeTypeDefinition d = schema.getAttributeType(s);
1770        if (d != null)
1771        {
1772          nfAllowedAttrs.add(d);
1773        }
1774      }
1775    }
1776
1777    boolean entryValid = true;
1778    final String[] attributeNames = rdn.getAttributeNames();
1779    final byte[][] attributeValues = rdn.getByteArrayAttributeValues();
1780    for (int i=0; i < attributeNames.length; i++)
1781    {
1782      final String name = attributeNames[i];
1783      if (checkEntryMissingRDNValues)
1784      {
1785        final byte[] value = attributeValues[i];
1786        final MatchingRule matchingRule =
1787             MatchingRule.selectEqualityMatchingRule(name, schema);
1788        if (! entry.hasAttributeValue(name, value, matchingRule))
1789        {
1790          entryValid = false;
1791          entriesMissingRDNValues.incrementAndGet();
1792          if (invalidReasons != null)
1793          {
1794            invalidReasons.add(ERR_ENTRY_MISSING_RDN_VALUE.get(
1795                 rdn.getAttributeValues()[i], name));
1796          }
1797        }
1798      }
1799
1800      final AttributeTypeDefinition d = schema.getAttributeType(name);
1801      if (d == null)
1802      {
1803        if (checkUndefinedAttributes)
1804        {
1805          entryValid = false;
1806          updateCount(name, undefinedAttributes);
1807          if (invalidReasons != null)
1808          {
1809            invalidReasons.add(ERR_ENTRY_RDN_ATTR_NOT_DEFINED.get(name));
1810          }
1811        }
1812      }
1813      else
1814      {
1815        if (checkProhibitedAttributes &&
1816            (! (requiredAttrs.contains(d) || optionalAttrs.contains(d) ||
1817                d.isOperational())))
1818        {
1819          entryValid = false;
1820          updateCount(d.getNameOrOID(), prohibitedAttributes);
1821          if (invalidReasons != null)
1822          {
1823            invalidReasons.add(ERR_ENTRY_RDN_ATTR_NOT_ALLOWED_IN_ENTRY.get(
1824                 d.getNameOrOID()));
1825          }
1826        }
1827
1828        if (checkNameForms && (nameForm != null))
1829        {
1830          if (! nfReqAttrs.remove(d))
1831          {
1832            if (! nfAllowedAttrs.contains(d))
1833            {
1834              if (entryValid)
1835              {
1836                entryValid = false;
1837                nameFormViolations.incrementAndGet();
1838              }
1839              if (invalidReasons != null)
1840              {
1841                invalidReasons.add(
1842                     ERR_ENTRY_RDN_ATTR_NOT_ALLOWED_BY_NF.get(name));
1843              }
1844            }
1845          }
1846        }
1847      }
1848    }
1849
1850    if (checkNameForms && (! nfReqAttrs.isEmpty()))
1851    {
1852      if (entryValid)
1853      {
1854        entryValid = false;
1855        nameFormViolations.incrementAndGet();
1856      }
1857      if (invalidReasons != null)
1858      {
1859        for (final AttributeTypeDefinition d : nfReqAttrs)
1860        {
1861          invalidReasons.add(ERR_ENTRY_RDN_MISSING_REQUIRED_ATTR.get(
1862               d.getNameOrOID()));
1863        }
1864      }
1865    }
1866
1867    return entryValid;
1868  }
1869
1870
1871
1872  /**
1873   * Updates the count for the given key in the provided map, adding a new key
1874   * with a count of one if necessary.
1875   *
1876   * @param  key  The key for which the count is to be updated.
1877   * @param  map  The map in which the update is to be made.
1878   */
1879  private static void updateCount(final String key,
1880                           final ConcurrentHashMap<String,AtomicLong> map)
1881  {
1882    final String lowerKey = StaticUtils.toLowerCase(key);
1883    AtomicLong l = map.get(lowerKey);
1884    if (l == null)
1885    {
1886      l = map.putIfAbsent(lowerKey, new AtomicLong(1L));
1887      if (l == null)
1888      {
1889        return;
1890      }
1891    }
1892
1893    l.incrementAndGet();
1894  }
1895
1896
1897
1898  /**
1899   * Resets all counts maintained by this entry validator.
1900   */
1901  public void resetCounts()
1902  {
1903    entriesExamined.set(0L);
1904    entriesMissingRDNValues.set(0L);
1905    invalidEntries.set(0L);
1906    malformedDNs.set(0L);
1907    missingSuperiorClasses.set(0L);
1908    multipleStructuralClasses.set(0L);
1909    nameFormViolations.set(0L);
1910    noObjectClasses.set(0L);
1911    noStructuralClass.set(0L);
1912
1913    attributesViolatingSyntax.clear();
1914    missingAttributes.clear();
1915    prohibitedAttributes.clear();
1916    prohibitedObjectClasses.clear();
1917    singleValueViolations.clear();
1918    undefinedAttributes.clear();
1919    undefinedObjectClasses.clear();
1920  }
1921
1922
1923
1924  /**
1925   * Retrieves the total number of entries examined during processing.
1926   *
1927   * @return  The total number of entries examined during processing.
1928   */
1929  public long getEntriesExamined()
1930  {
1931    return entriesExamined.get();
1932  }
1933
1934
1935
1936  /**
1937   * Retrieves the total number of invalid entries encountered during
1938   * processing.
1939   *
1940   * @return  The total number of invalid entries encountered during processing.
1941   */
1942  public long getInvalidEntries()
1943  {
1944    return invalidEntries.get();
1945  }
1946
1947
1948
1949  /**
1950   * Retrieves the total number of entries examined that had malformed DNs which
1951   * could not be parsed.
1952   *
1953   * @return  The total number of entries examined that had malformed DNs.
1954   */
1955  public long getMalformedDNs()
1956  {
1957    return malformedDNs.get();
1958  }
1959
1960
1961
1962  /**
1963   * Retrieves the total number of entries examined that included an attribute
1964   * value in the RDN that was not present in the entry attributes.
1965   *
1966   * @return  The total number of entries examined that included an attribute
1967   *          value in the RDN that was not present in the entry attributes.
1968   */
1969  public long getEntriesMissingRDNValues()
1970  {
1971    return entriesMissingRDNValues.get();
1972  }
1973
1974
1975
1976  /**
1977   * Retrieves the total number of entries examined which did not contain any
1978   * object classes.
1979   *
1980   * @return  The total number of entries examined which did not contain any
1981   *          object classes.
1982   */
1983  public long getEntriesWithoutAnyObjectClasses()
1984  {
1985    return noObjectClasses.get();
1986  }
1987
1988
1989
1990  /**
1991   * Retrieves the total number of entries examined which did not contain any
1992   * structural object class.
1993   *
1994   * @return  The total number of entries examined which did not contain any
1995   *          structural object class.
1996   */
1997  public long getEntriesMissingStructuralObjectClass()
1998  {
1999    return noStructuralClass.get();
2000  }
2001
2002
2003
2004  /**
2005   * Retrieves the total number of entries examined which contained more than
2006   * one structural object class.
2007   *
2008   * @return  The total number of entries examined which contained more than one
2009   *          structural object class.
2010   */
2011  public long getEntriesWithMultipleStructuralObjectClasses()
2012  {
2013    return multipleStructuralClasses.get();
2014  }
2015
2016
2017
2018  /**
2019   * Retrieves the total number of entries examined which were missing one or
2020   * more superior object classes.
2021   *
2022   * @return  The total number of entries examined which were missing one or
2023   *          more superior object classes.
2024   */
2025  public long getEntriesWithMissingSuperiorObjectClasses()
2026  {
2027    return missingSuperiorClasses.get();
2028  }
2029
2030
2031
2032  /**
2033   * Retrieves the total number of entries examined which contained an RDN that
2034   * violated the constraints of the associated name form.
2035   *
2036   * @return  The total number of entries examined which contained an RDN that
2037   *          violated the constraints of the associated name form.
2038   */
2039  public long getNameFormViolations()
2040  {
2041    return nameFormViolations.get();
2042  }
2043
2044
2045
2046  /**
2047   * Retrieves the total number of undefined object classes encountered while
2048   * examining entries.  Note that this number may be greater than the total
2049   * number of entries examined if entries contain multiple undefined object
2050   * classes.
2051   *
2052   * @return  The total number of undefined object classes encountered while
2053   *          examining entries.
2054   */
2055  public long getTotalUndefinedObjectClasses()
2056  {
2057    return getMapTotal(undefinedObjectClasses);
2058  }
2059
2060
2061
2062  /**
2063   * Retrieves the undefined object classes encountered while processing
2064   * entries, mapped from the name of the undefined object class to the number
2065   * of entries in which that object class was referenced.
2066   *
2067   * @return  The undefined object classes encountered while processing entries.
2068   */
2069  public Map<String,Long> getUndefinedObjectClasses()
2070  {
2071    return convertMap(undefinedObjectClasses);
2072  }
2073
2074
2075
2076  /**
2077   * Retrieves the total number of undefined attribute types encountered while
2078   * examining entries.  Note that this number may be greater than the total
2079   * number of entries examined if entries contain multiple undefined attribute
2080   * types.
2081   *
2082   * @return  The total number of undefined attribute types encountered while
2083   *          examining entries.
2084   */
2085  public long getTotalUndefinedAttributes()
2086  {
2087    return getMapTotal(undefinedAttributes);
2088  }
2089
2090
2091
2092  /**
2093   * Retrieves the undefined attribute types encountered while processing
2094   * entries, mapped from the name of the undefined attribute to the number
2095   * of entries in which that attribute type was referenced.
2096   *
2097   * @return  The undefined attribute types encountered while processing
2098   *          entries.
2099   */
2100  public Map<String,Long> getUndefinedAttributes()
2101  {
2102    return convertMap(undefinedAttributes);
2103  }
2104
2105
2106
2107  /**
2108   * Retrieves the total number of prohibited object classes encountered while
2109   * examining entries.  Note that this number may be greater than the total
2110   * number of entries examined if entries contain multiple prohibited object
2111   * classes.
2112   *
2113   * @return  The total number of prohibited object classes encountered while
2114   *          examining entries.
2115   */
2116  public long getTotalProhibitedObjectClasses()
2117  {
2118    return getMapTotal(prohibitedObjectClasses);
2119  }
2120
2121
2122
2123  /**
2124   * Retrieves the prohibited object classes encountered while processing
2125   * entries, mapped from the name of the object class to the number of entries
2126   * in which that object class was referenced.
2127   *
2128   * @return  The prohibited object classes encountered while processing
2129   *          entries.
2130   */
2131  public Map<String,Long> getProhibitedObjectClasses()
2132  {
2133    return convertMap(prohibitedObjectClasses);
2134  }
2135
2136
2137
2138  /**
2139   * Retrieves the total number of prohibited attributes encountered while
2140   * examining entries.  Note that this number may be greater than the total
2141   * number of entries examined if entries contain multiple prohibited
2142   * attributes.
2143   *
2144   * @return  The total number of prohibited attributes encountered while
2145   *          examining entries.
2146   */
2147  public long getTotalProhibitedAttributes()
2148  {
2149    return getMapTotal(prohibitedAttributes);
2150  }
2151
2152
2153
2154  /**
2155   * Retrieves the prohibited attributes encountered while processing entries,
2156   * mapped from the name of the attribute to the number of entries in which
2157   * that attribute was referenced.
2158   *
2159   * @return  The prohibited attributes encountered while processing entries.
2160   */
2161  public Map<String,Long> getProhibitedAttributes()
2162  {
2163    return convertMap(prohibitedAttributes);
2164  }
2165
2166
2167
2168  /**
2169   * Retrieves the total number of missing required attributes encountered while
2170   * examining entries.  Note that this number may be greater than the total
2171   * number of entries examined if entries are missing multiple attributes.
2172   *
2173   * @return  The total number of missing required attributes encountered while
2174   *          examining entries.
2175   */
2176  public long getTotalMissingAttributes()
2177  {
2178    return getMapTotal(missingAttributes);
2179  }
2180
2181
2182
2183  /**
2184   * Retrieves the missing required encountered while processing entries, mapped
2185   * from the name of the attribute to the number of entries in which that
2186   * attribute was required but not found.
2187   *
2188   * @return  The prohibited attributes encountered while processing entries.
2189   */
2190  public Map<String,Long> getMissingAttributes()
2191  {
2192    return convertMap(missingAttributes);
2193  }
2194
2195
2196
2197  /**
2198   * Retrieves the total number of attribute values which violate their
2199   * associated syntax that were encountered while examining entries.  Note that
2200   * this number may be greater than the total number of entries examined if
2201   * entries contain multiple malformed attribute values.
2202   *
2203   * @return  The total number of attribute values which violate their
2204   *          associated syntax that were encountered while examining entries.
2205   */
2206  public long getTotalAttributesViolatingSyntax()
2207  {
2208    return getMapTotal(attributesViolatingSyntax);
2209  }
2210
2211
2212
2213  /**
2214   * Retrieves the attributes with values violating their associated syntax that
2215   * were encountered while processing entries, mapped from the name of the
2216   * attribute to the number of malformed values found for that attribute.
2217   *
2218   * @return  The attributes with malformed values encountered while processing
2219   *          entries.
2220   */
2221  public Map<String,Long> getAttributesViolatingSyntax()
2222  {
2223    return convertMap(attributesViolatingSyntax);
2224  }
2225
2226
2227
2228  /**
2229   * Retrieves the total number of attributes defined as single-valued that
2230   * contained multiple values which were encountered while processing entries.
2231   * Note that this number may be greater than the total number of entries
2232   * examined if entries contain multiple such attributes.
2233   *
2234   * @return  The total number of attribute defined as single-valued that
2235   *          contained multiple values which were encountered while processing
2236   *          entries.
2237   */
2238  public long getTotalSingleValueViolations()
2239  {
2240    return getMapTotal(singleValueViolations);
2241  }
2242
2243
2244
2245  /**
2246   * Retrieves the attributes defined as single-valued that contained multiple
2247   * values which were encountered while processing entries, mapped from the
2248   * name of the attribute to the number of entries in which that attribute had
2249   * multiple values.
2250   *
2251   * @return  The attributes defined as single-valued that contained multiple
2252   *          values which were encountered while processing entries.
2253   */
2254  public Map<String,Long> getSingleValueViolations()
2255  {
2256    return convertMap(singleValueViolations);
2257  }
2258
2259
2260
2261  /**
2262   * Retrieves the total number of occurrences for all items in the provided
2263   * map.
2264   *
2265   * @param  map  The map to be processed.
2266   *
2267   * @return  The total number of occurrences for all items in the provided map.
2268   */
2269  private static long getMapTotal(final Map<String,AtomicLong> map)
2270  {
2271    long total = 0L;
2272
2273    for (final AtomicLong l : map.values())
2274    {
2275      total += l.longValue();
2276    }
2277
2278    return total;
2279  }
2280
2281
2282
2283  /**
2284   * Converts the provided map from strings to atomic longs to a map from
2285   * strings to longs.
2286   *
2287   * @param  map  The map to be processed.
2288   *
2289   * @return  The new map.
2290   */
2291  private static Map<String,Long> convertMap(final Map<String,AtomicLong> map)
2292  {
2293    final TreeMap<String,Long> m = new TreeMap<>();
2294    for (final Map.Entry<String,AtomicLong> e : map.entrySet())
2295    {
2296      m.put(e.getKey(), e.getValue().longValue());
2297    }
2298
2299    return Collections.unmodifiableMap(m);
2300  }
2301
2302
2303
2304  /**
2305   * Retrieves a list of messages providing a summary of the invalid entries
2306   * processed by this class.
2307   *
2308   * @param  detailedResults  Indicates whether to include detailed information
2309   *                          about the attributes and object classes
2310   *                          responsible for the violations.
2311   *
2312   * @return  A list of messages providing a summary of the invalid entries
2313   *          processed by this class, or an empty list if all entries examined
2314   *          were valid.
2315   */
2316  public List<String> getInvalidEntrySummary(final boolean detailedResults)
2317  {
2318    final long numInvalid = invalidEntries.get();
2319    if (numInvalid == 0)
2320    {
2321      return Collections.emptyList();
2322    }
2323
2324    final ArrayList<String> messages = new ArrayList<>(5);
2325    final long numEntries = entriesExamined.get();
2326    long pct = 100 * numInvalid / numEntries;
2327    messages.add(INFO_ENTRY_INVALID_ENTRY_COUNT.get(
2328         numInvalid, numEntries, pct));
2329
2330    final long numBadDNs = malformedDNs.get();
2331    if (numBadDNs > 0)
2332    {
2333      pct = 100 * numBadDNs / numEntries;
2334      messages.add(INFO_ENTRY_MALFORMED_DN_COUNT.get(
2335           numBadDNs, numEntries, pct));
2336    }
2337
2338    final long numEntriesMissingRDNValues = entriesMissingRDNValues.get();
2339    if (numEntriesMissingRDNValues > 0)
2340    {
2341      pct = 100* numEntriesMissingRDNValues / numEntries;
2342      messages.add(INFO_ENTRY_MISSING_RDN_VALUE_COUNT.get(
2343           numEntriesMissingRDNValues, numEntries, pct));
2344    }
2345
2346    final long numNoOCs = noObjectClasses.get();
2347    if (numNoOCs > 0)
2348    {
2349      pct = 100 * numNoOCs / numEntries;
2350      messages.add(INFO_ENTRY_NO_OC_COUNT.get(numNoOCs, numEntries, pct));
2351    }
2352
2353    final long numMissingStructural = noStructuralClass.get();
2354    if (numMissingStructural > 0)
2355    {
2356      pct = 100 * numMissingStructural / numEntries;
2357      messages.add(INFO_ENTRY_NO_STRUCTURAL_OC_COUNT.get(
2358           numMissingStructural, numEntries, pct));
2359    }
2360
2361    final long numMultipleStructural = multipleStructuralClasses.get();
2362    if (numMultipleStructural > 0)
2363    {
2364      pct = 100 * numMultipleStructural / numEntries;
2365      messages.add(INFO_ENTRY_MULTIPLE_STRUCTURAL_OCS_COUNT.get(
2366           numMultipleStructural, numEntries, pct));
2367    }
2368
2369    final long numNFViolations = nameFormViolations.get();
2370    if (numNFViolations > 0)
2371    {
2372      pct = 100 * numNFViolations / numEntries;
2373      messages.add(INFO_ENTRY_NF_VIOLATION_COUNT.get(
2374           numNFViolations, numEntries, pct));
2375    }
2376
2377    final long numUndefinedOCs = getTotalUndefinedObjectClasses();
2378    if (numUndefinedOCs > 0)
2379    {
2380      messages.add(INFO_ENTRY_UNDEFINED_OC_COUNT.get(numUndefinedOCs));
2381      if (detailedResults)
2382      {
2383        for (final Map.Entry<String,AtomicLong> e :
2384             undefinedObjectClasses.entrySet())
2385        {
2386          messages.add(INFO_ENTRY_UNDEFINED_OC_NAME_COUNT.get(
2387               e.getKey(), e.getValue().longValue()));
2388        }
2389      }
2390    }
2391
2392    final long numProhibitedOCs = getTotalProhibitedObjectClasses();
2393    if (numProhibitedOCs > 0)
2394    {
2395      messages.add(INFO_ENTRY_PROHIBITED_OC_COUNT.get(numProhibitedOCs));
2396      if (detailedResults)
2397      {
2398        for (final Map.Entry<String,AtomicLong> e :
2399             prohibitedObjectClasses.entrySet())
2400        {
2401          messages.add(INFO_ENTRY_PROHIBITED_OC_NAME_COUNT.get(
2402               e.getKey(), e.getValue().longValue()));
2403        }
2404      }
2405    }
2406
2407    final long numMissingSuperior =
2408         getEntriesWithMissingSuperiorObjectClasses();
2409    if (numMissingSuperior > 0)
2410    {
2411      messages.add(
2412           INFO_ENTRY_MISSING_SUPERIOR_OC_COUNT.get(numMissingSuperior));
2413    }
2414
2415    final long numUndefinedAttrs = getTotalUndefinedAttributes();
2416    if (numUndefinedAttrs > 0)
2417    {
2418      messages.add(INFO_ENTRY_UNDEFINED_ATTR_COUNT.get(numUndefinedAttrs));
2419      if (detailedResults)
2420      {
2421        for (final Map.Entry<String,AtomicLong> e :
2422             undefinedAttributes.entrySet())
2423        {
2424          messages.add(INFO_ENTRY_UNDEFINED_ATTR_NAME_COUNT.get(
2425               e.getKey(), e.getValue().longValue()));
2426        }
2427      }
2428    }
2429
2430    final long numMissingAttrs = getTotalMissingAttributes();
2431    if (numMissingAttrs > 0)
2432    {
2433      messages.add(INFO_ENTRY_MISSING_ATTR_COUNT.get(numMissingAttrs));
2434      if (detailedResults)
2435      {
2436        for (final Map.Entry<String,AtomicLong> e :
2437             missingAttributes.entrySet())
2438        {
2439          messages.add(INFO_ENTRY_MISSING_ATTR_NAME_COUNT.get(
2440               e.getKey(), e.getValue().longValue()));
2441        }
2442      }
2443    }
2444
2445    final long numProhibitedAttrs = getTotalProhibitedAttributes();
2446    if (numProhibitedAttrs > 0)
2447    {
2448      messages.add(INFO_ENTRY_PROHIBITED_ATTR_COUNT.get(numProhibitedAttrs));
2449      if (detailedResults)
2450      {
2451        for (final Map.Entry<String,AtomicLong> e :
2452             prohibitedAttributes.entrySet())
2453        {
2454          messages.add(INFO_ENTRY_PROHIBITED_ATTR_NAME_COUNT.get(
2455               e.getKey(), e.getValue().longValue()));
2456        }
2457      }
2458    }
2459
2460    final long numSingleValuedViolations = getTotalSingleValueViolations();
2461    if (numSingleValuedViolations > 0)
2462    {
2463      messages.add(INFO_ENTRY_SINGLE_VALUE_VIOLATION_COUNT.get(
2464           numSingleValuedViolations));
2465      if (detailedResults)
2466      {
2467        for (final Map.Entry<String,AtomicLong> e :
2468             singleValueViolations.entrySet())
2469        {
2470          messages.add(INFO_ENTRY_SINGLE_VALUE_VIOLATION_NAME_COUNT.get(
2471               e.getKey(), e.getValue().longValue()));
2472        }
2473      }
2474    }
2475
2476    final long numSyntaxViolations = getTotalAttributesViolatingSyntax();
2477    if (numSyntaxViolations > 0)
2478    {
2479      messages.add(INFO_ENTRY_SYNTAX_VIOLATION_COUNT.get(numSyntaxViolations));
2480      if (detailedResults)
2481      {
2482        for (final Map.Entry<String,AtomicLong> e :
2483             attributesViolatingSyntax.entrySet())
2484        {
2485          messages.add(INFO_ENTRY_SYNTAX_VIOLATION_NAME_COUNT.get(
2486               e.getKey(), e.getValue().longValue()));
2487        }
2488      }
2489    }
2490
2491    return Collections.unmodifiableList(messages);
2492  }
2493}