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