001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk.schema;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashSet;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.TreeMap;
033    import java.util.concurrent.ConcurrentHashMap;
034    import java.util.concurrent.atomic.AtomicLong;
035    import java.util.concurrent.atomic.AtomicReference;
036    
037    import com.unboundid.asn1.ASN1OctetString;
038    import com.unboundid.ldap.matchingrules.MatchingRule;
039    import com.unboundid.ldap.sdk.Attribute;
040    import com.unboundid.ldap.sdk.Entry;
041    import com.unboundid.ldap.sdk.LDAPException;
042    import com.unboundid.ldap.sdk.RDN;
043    import com.unboundid.util.ThreadSafety;
044    import com.unboundid.util.ThreadSafetyLevel;
045    
046    import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
047    import static com.unboundid.util.Debug.*;
048    import static com.unboundid.util.StaticUtils.*;
049    import 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)
099    public 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    }