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