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