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