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