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