001/*
002 * Copyright 2008-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.schema;
022
023
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.TreeMap;
035import java.util.concurrent.ConcurrentHashMap;
036import java.util.concurrent.atomic.AtomicLong;
037import java.util.concurrent.atomic.AtomicReference;
038import java.util.regex.Pattern;
039
040import com.unboundid.asn1.ASN1OctetString;
041import com.unboundid.ldap.matchingrules.MatchingRule;
042import com.unboundid.ldap.sdk.Attribute;
043import com.unboundid.ldap.sdk.Entry;
044import com.unboundid.ldap.sdk.LDAPException;
045import com.unboundid.ldap.sdk.RDN;
046import com.unboundid.util.ThreadSafety;
047import com.unboundid.util.ThreadSafetyLevel;
048
049import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
050import static com.unboundid.util.Debug.*;
051import static com.unboundid.util.StaticUtils.*;
052import 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)
104public final class EntryValidator
105       implements Serializable
106{
107  /**
108   * The serial version UID for this serializable class.
109   */
110  private static final long serialVersionUID = -8945609557086398241L;
111
112
113
114  // A count of the total number of entries examined.
115  private final AtomicLong entriesExamined;
116
117  // A count of the number of entries missing an attribute value contained in
118  // the RDN.
119  private final AtomicLong entriesMissingRDNValues;
120
121  // A count of the total number of invalid entries encountered.
122  private final AtomicLong invalidEntries;
123
124  // A count of the number of entries with DNs that could not be parsed.
125  private final AtomicLong malformedDNs;
126
127  // A count of the number of entries missing a superior object class.
128  private final AtomicLong missingSuperiorClasses;
129
130  // A count of the number of entries containing multiple structural object
131  // classes.
132  private final AtomicLong multipleStructuralClasses;
133
134  // A count of the number of entries with RDNs that violate the associated
135  // name form.
136  private final AtomicLong nameFormViolations;
137
138  // A count of the number of entries without any object class.
139  private final AtomicLong noObjectClasses;
140
141  // A count of the number of entries without a structural object class.
142  private final AtomicLong noStructuralClass;
143
144  // Indicates whether an entry should be considered invalid if it contains an
145  // attribute value which violates the associated attribute syntax.
146  private boolean checkAttributeSyntax;
147
148  // Indicates whether an entry should be considered invalid if it contains one
149  // or more attribute values in its RDN that are not present in the set of
150  // entry attributes.
151  private boolean checkEntryMissingRDNValues;
152
153  // Indicates whether an entry should be considered invalid if its DN cannot be
154  // parsed.
155  private boolean checkMalformedDNs;
156
157  // Indicates whether an entry should be considered invalid if it is missing
158  // attributes required by its object classes or DIT content rule.
159  private boolean checkMissingAttributes;
160
161  // Indicates whether an entry should be considered invalid if it is missing
162  // one or more superior object classes.
163  private boolean checkMissingSuperiorObjectClasses;
164
165  // Indicates whether an entry should be considered invalid if its RDN does not
166  // conform to name form requirements.
167  private boolean checkNameForms;
168
169  // Indicates whether an entry should be considered invalid if it contains any
170  // attributes which are not allowed by its object classes or DIT content rule.
171  private boolean checkProhibitedAttributes;
172
173  // Indicates whether an entry should be considered invalid if it contains an
174  // auxiliary class that is not allowed by its DIT content rule or an abstract
175  // class that is not associated with a non-abstract class.
176  private boolean checkProhibitedObjectClasses;
177
178  // Indicates whether an entry should be considered invalid if it contains any
179  // attribute defined as single-valued with more than one values.
180  private boolean checkSingleValuedAttributes;
181
182  // Indicates whether an entry should be considered invalid if it does not
183  // contain exactly one structural object class.
184  private boolean checkStructuralObjectClasses;
185
186  // Indicates whether an entry should be considered invalid if it contains an
187  // attribute which is not defined in the schema.
188  private boolean checkUndefinedAttributes;
189
190  // Indicates whether an entry should be considered invalid if it contains an
191  // object class which is not defined in the schema.
192  private boolean checkUndefinedObjectClasses;
193
194  // A map of the attributes with values violating the associated syntax to the
195  // number of values found violating the syntax.
196  private final ConcurrentHashMap<String,AtomicLong> attributesViolatingSyntax;
197
198  // A map of the required attribute types that were missing from entries to
199  // the number of entries missing them.
200  private final ConcurrentHashMap<String,AtomicLong> missingAttributes;
201
202  // A map of the prohibited attribute types that were included in entries to
203  // the number of entries referencing them.
204  private final ConcurrentHashMap<String,AtomicLong> prohibitedAttributes;
205
206  // A map of the prohibited auxiliary object classes that were included in
207  // entries to the number of entries referencing them.
208  private final ConcurrentHashMap<String,AtomicLong> prohibitedObjectClasses;
209
210  // A map of the single-valued attributes with multiple values to the number
211  // of entries with multiple values for those attributes.
212  private final ConcurrentHashMap<String,AtomicLong> singleValueViolations;
213
214  // A map of undefined attribute types to the number of entries referencing
215  // them.
216  private final ConcurrentHashMap<String,AtomicLong> undefinedAttributes;
217
218  // A map of undefined object classes to the number of entries referencing
219  // them.
220  private final ConcurrentHashMap<String,AtomicLong> undefinedObjectClasses;
221
222  // The schema against which entries will be validated.
223  private final Schema schema;
224
225  // The attribute types for which to ignore syntax violations.
226  private Set<AttributeTypeDefinition> ignoreSyntaxViolationTypes;
227
228
229
230  /**
231   * Creates a new entry validator that will validate entries according to the
232   * provided schema.
233   *
234   * @param  schema  The schema against which entries will be validated.
235   */
236  public EntryValidator(final Schema schema)
237  {
238    this.schema = schema;
239
240    checkAttributeSyntax              = true;
241    checkEntryMissingRDNValues        = true;
242    checkMalformedDNs                 = true;
243    checkMissingAttributes            = true;
244    checkMissingSuperiorObjectClasses = true;
245    checkNameForms                    = true;
246    checkProhibitedAttributes         = true;
247    checkProhibitedObjectClasses      = true;
248    checkSingleValuedAttributes       = true;
249    checkStructuralObjectClasses      = true;
250    checkUndefinedAttributes          = true;
251    checkUndefinedObjectClasses       = true;
252
253    ignoreSyntaxViolationTypes = Collections.emptySet();
254
255    entriesExamined           = new AtomicLong(0L);
256    entriesMissingRDNValues   = new AtomicLong(0L);
257    invalidEntries            = new AtomicLong(0L);
258    malformedDNs              = new AtomicLong(0L);
259    missingSuperiorClasses    = new AtomicLong(0L);
260    multipleStructuralClasses = new AtomicLong(0L);
261    nameFormViolations        = new AtomicLong(0L);
262    noObjectClasses           = new AtomicLong(0L);
263    noStructuralClass         = new AtomicLong(0L);
264
265    attributesViolatingSyntax = 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}