001/* 002 * Copyright 2007-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2023 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2023 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.schema; 037 038 039 040import java.io.File; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.Serializable; 044import java.util.ArrayList; 045import java.util.Arrays; 046import java.util.Collections; 047import java.util.LinkedHashMap; 048import java.util.LinkedHashSet; 049import java.util.List; 050import java.util.Map; 051import java.util.Set; 052import java.util.concurrent.atomic.AtomicReference; 053 054import com.unboundid.ldap.sdk.Attribute; 055import com.unboundid.ldap.sdk.Entry; 056import com.unboundid.ldap.sdk.Filter; 057import com.unboundid.ldap.sdk.LDAPConnection; 058import com.unboundid.ldap.sdk.LDAPException; 059import com.unboundid.ldap.sdk.ReadOnlyEntry; 060import com.unboundid.ldap.sdk.ResultCode; 061import com.unboundid.ldap.sdk.SearchScope; 062import com.unboundid.ldif.LDIFException; 063import com.unboundid.ldif.LDIFReader; 064import com.unboundid.util.Debug; 065import com.unboundid.util.NotMutable; 066import com.unboundid.util.NotNull; 067import com.unboundid.util.Nullable; 068import com.unboundid.util.StaticUtils; 069import com.unboundid.util.ThreadSafety; 070import com.unboundid.util.ThreadSafetyLevel; 071import com.unboundid.util.Validator; 072 073import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 074 075 076 077/** 078 * This class provides a data structure for representing a directory server 079 * subschema subentry. This includes information about the attribute syntaxes, 080 * matching rules, attribute types, object classes, name forms, DIT content 081 * rules, DIT structure rules, and matching rule uses defined in the server 082 * schema. 083 */ 084@NotMutable() 085@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 086public final class Schema 087 implements Serializable 088{ 089 /** 090 * The name of the attribute used to hold the attribute syntax definitions. 091 */ 092 @NotNull public static final String ATTR_ATTRIBUTE_SYNTAX = "ldapSyntaxes"; 093 094 095 096 /** 097 * The name of the attribute used to hold the attribute type definitions. 098 */ 099 @NotNull public static final String ATTR_ATTRIBUTE_TYPE = "attributeTypes"; 100 101 102 103 /** 104 * The name of the attribute used to hold the DIT content rule definitions. 105 */ 106 @NotNull public static final String ATTR_DIT_CONTENT_RULE = "dITContentRules"; 107 108 109 110 /** 111 * The name of the attribute used to hold the DIT structure rule definitions. 112 */ 113 @NotNull public static final String ATTR_DIT_STRUCTURE_RULE = 114 "dITStructureRules"; 115 116 117 118 /** 119 * The name of the attribute used to hold the matching rule definitions. 120 */ 121 @NotNull public static final String ATTR_MATCHING_RULE = "matchingRules"; 122 123 124 125 /** 126 * The name of the attribute used to hold the matching rule use definitions. 127 */ 128 @NotNull public static final String ATTR_MATCHING_RULE_USE = 129 "matchingRuleUse"; 130 131 132 133 /** 134 * The name of the attribute used to hold the name form definitions. 135 */ 136 @NotNull public static final String ATTR_NAME_FORM = "nameForms"; 137 138 139 140 /** 141 * The name of the attribute used to hold the object class definitions. 142 */ 143 @NotNull public static final String ATTR_OBJECT_CLASS = "objectClasses"; 144 145 146 147 /** 148 * The name of the attribute used to hold the DN of the subschema subentry 149 * with the schema information that governs a specified entry. 150 */ 151 @NotNull public static final String ATTR_SUBSCHEMA_SUBENTRY = 152 "subschemaSubentry"; 153 154 155 156 /** 157 * The default standard schema available for use in the LDAP SDK. 158 */ 159 @NotNull private static final AtomicReference<Schema> 160 DEFAULT_STANDARD_SCHEMA = new AtomicReference<>(); 161 162 163 164 /** 165 * The set of request attributes that will be used when retrieving the server 166 * subschema subentry in order to retrieve all of the schema elements. 167 */ 168 @NotNull private static final String[] SCHEMA_REQUEST_ATTRS = 169 { 170 "*", 171 ATTR_ATTRIBUTE_SYNTAX, 172 ATTR_ATTRIBUTE_TYPE, 173 ATTR_DIT_CONTENT_RULE, 174 ATTR_DIT_STRUCTURE_RULE, 175 ATTR_MATCHING_RULE, 176 ATTR_MATCHING_RULE_USE, 177 ATTR_NAME_FORM, 178 ATTR_OBJECT_CLASS 179 }; 180 181 182 183 /** 184 * The set of request attributes that will be used when retrieving the 185 * subschema subentry attribute from a specified entry in order to determine 186 * the location of the server schema definitions. 187 */ 188 @NotNull private static final String[] SUBSCHEMA_SUBENTRY_REQUEST_ATTRS = 189 { 190 ATTR_SUBSCHEMA_SUBENTRY 191 }; 192 193 194 195 /** 196 * Retrieves the resource path that may be used to obtain a file with a number 197 * of standard schema definitions. 198 */ 199 @NotNull private static final String DEFAULT_SCHEMA_RESOURCE_PATH = 200 "com/unboundid/ldap/sdk/schema/standard-schema.ldif"; 201 202 203 204 /** 205 * The serial version UID for this serializable class. 206 */ 207 private static final long serialVersionUID = 8081839633831517925L; 208 209 210 211 // A map of all subordinate attribute type definitions for each attribute 212 // type definition. 213 @NotNull private final 214 Map<AttributeTypeDefinition,List<AttributeTypeDefinition>> 215 subordinateAttributeTypes; 216 217 // The set of attribute syntaxes mapped from lowercase name/OID to syntax. 218 @NotNull private final Map<String,AttributeSyntaxDefinition> asMap; 219 220 // The set of attribute types mapped from lowercase name/OID to type. 221 @NotNull private final Map<String,AttributeTypeDefinition> atMap; 222 223 // The set of DIT content rules mapped from lowercase name/OID to rule. 224 @NotNull private final Map<String,DITContentRuleDefinition> dcrMap; 225 226 // The set of DIT structure rules mapped from rule ID to rule. 227 @NotNull private final Map<Integer,DITStructureRuleDefinition> dsrMapByID; 228 229 // The set of DIT structure rules mapped from lowercase name to rule. 230 @NotNull private final Map<String,DITStructureRuleDefinition> dsrMapByName; 231 232 // The set of DIT structure rules mapped from lowercase name to rule. 233 @NotNull private final Map<String,DITStructureRuleDefinition> 234 dsrMapByNameForm; 235 236 // The set of matching rules mapped from lowercase name/OID to rule. 237 @NotNull private final Map<String,MatchingRuleDefinition> mrMap; 238 239 // The set of matching rule uses mapped from matching rule OID to use. 240 @NotNull private final Map<String,MatchingRuleUseDefinition> mruMap; 241 242 // The set of name forms mapped from lowercase name/OID to name form. 243 @NotNull private final Map<String,NameFormDefinition> nfMapByName; 244 245 // The set of name forms mapped from structural class OID to name form. 246 @NotNull private final Map<String,NameFormDefinition> nfMapByOC; 247 248 // The set of object classes mapped from lowercase name/OID to class. 249 @NotNull private final Map<String,ObjectClassDefinition> ocMap; 250 251 // The entry used to create this schema object. 252 @NotNull private final ReadOnlyEntry schemaEntry; 253 254 // The set of attribute syntaxes defined in the schema. 255 @NotNull private final Set<AttributeSyntaxDefinition> asSet; 256 257 // The set of attribute types defined in the schema. 258 @NotNull private final Set<AttributeTypeDefinition> atSet; 259 260 // The set of operational attribute types defined in the schema. 261 @NotNull private final Set<AttributeTypeDefinition> operationalATSet; 262 263 // The set of user attribute types defined in the schema. 264 @NotNull private final Set<AttributeTypeDefinition> userATSet; 265 266 // The set of DIT content rules defined in the schema. 267 @NotNull private final Set<DITContentRuleDefinition> dcrSet; 268 269 // The set of DIT structure rules defined in the schema. 270 @NotNull private final Set<DITStructureRuleDefinition> dsrSet; 271 272 // The set of matching rules defined in the schema. 273 @NotNull private final Set<MatchingRuleDefinition> mrSet; 274 275 // The set of matching rule uses defined in the schema. 276 @NotNull private final Set<MatchingRuleUseDefinition> mruSet; 277 278 // The set of name forms defined in the schema. 279 @NotNull private final Set<NameFormDefinition> nfSet; 280 281 // The set of object classes defined in the schema. 282 @NotNull private final Set<ObjectClassDefinition> ocSet; 283 284 // The set of abstract object classes defined in the schema. 285 @NotNull private final Set<ObjectClassDefinition> abstractOCSet; 286 287 // The set of auxiliary object classes defined in the schema. 288 @NotNull private final Set<ObjectClassDefinition> auxiliaryOCSet; 289 290 // The set of structural object classes defined in the schema. 291 @NotNull private final Set<ObjectClassDefinition> structuralOCSet; 292 293 294 295 /** 296 * Creates a new schema object by decoding the information in the provided 297 * entry. Any schema elements that cannot be parsed will be silently ignored. 298 * 299 * @param schemaEntry The schema entry to decode. It must not be 300 * {@code null}. 301 */ 302 public Schema(@NotNull final Entry schemaEntry) 303 { 304 this(schemaEntry, null, null, null, null, null, null, null, null); 305 } 306 307 308 309 /** 310 * Creates a new schema object by decoding the information in the provided 311 * entry, optionally capturing any information about unparsable values in the 312 * provided maps. 313 * 314 * @param schemaEntry The schema entry to decode. It must 315 * not be {@code null}. 316 * @param unparsableAttributeSyntaxes A map that will be updated with 317 * information about any attribute syntax 318 * definitions that cannot be parsed. It 319 * may be {@code null} if unparsable 320 * attribute syntax definitions should be 321 * silently ignored. 322 * @param unparsableMatchingRules A map that will be updated with 323 * information about any matching rule 324 * definitions that cannot be parsed. It 325 * may be {@code null} if unparsable 326 * matching rule definitions should be 327 * silently ignored. 328 * @param unparsableAttributeTypes A map that will be updated with 329 * information about any attribute type 330 * definitions that cannot be parsed. It 331 * may be {@code null} if unparsable 332 * attribute type definitions should be 333 * silently ignored. 334 * @param unparsableObjectClasses A map that will be updated with 335 * information about any object class 336 * definitions that cannot be parsed. It 337 * may be {@code null} if unparsable 338 * object class definitions should be 339 * silently ignored. 340 * @param unparsableDITContentRules A map that will be updated with 341 * information about any DIT content rule 342 * definitions that cannot be parsed. It 343 * may be {@code null} if unparsable 344 * DIT content rule definitions should be 345 * silently ignored. 346 * @param unparsableDITStructureRules A map that will be updated with 347 * information about any DIT structure 348 * rule definitions that cannot be 349 * parsed. It may be {@code null} if 350 * unparsable attribute DIT structure 351 * rule definitions should be silently 352 * ignored. 353 * @param unparsableNameForms A map that will be updated with 354 * information about any name form 355 * definitions that cannot be parsed. It 356 * may be {@code null} if unparsable 357 * name form definitions should be 358 * silently ignored. 359 * @param unparsableMatchingRuleUses A map that will be updated with 360 * information about any matching rule 361 * use definitions that cannot be parsed. 362 * It may be {@code null} if unparsable 363 * matching rule use definitions should 364 * be silently ignored. 365 */ 366 public Schema(@NotNull final Entry schemaEntry, 367 @Nullable final Map<String,LDAPException> unparsableAttributeSyntaxes, 368 @Nullable final Map<String,LDAPException> unparsableMatchingRules, 369 @Nullable final Map<String,LDAPException> unparsableAttributeTypes, 370 @Nullable final Map<String,LDAPException> unparsableObjectClasses, 371 @Nullable final Map<String,LDAPException> unparsableDITContentRules, 372 @Nullable final Map<String,LDAPException> unparsableDITStructureRules, 373 @Nullable final Map<String,LDAPException> unparsableNameForms, 374 @Nullable final Map<String,LDAPException> unparsableMatchingRuleUses) 375 { 376 this.schemaEntry = new ReadOnlyEntry(schemaEntry); 377 378 // Decode the attribute syntaxes from the schema entry. 379 String[] defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_SYNTAX); 380 if (defs == null) 381 { 382 asMap = Collections.emptyMap(); 383 asSet = Collections.emptySet(); 384 } 385 else 386 { 387 final LinkedHashMap<String,AttributeSyntaxDefinition> m = 388 new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length)); 389 final LinkedHashSet<AttributeSyntaxDefinition> s = 390 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 391 392 for (final String def : defs) 393 { 394 try 395 { 396 final AttributeSyntaxDefinition as = 397 new AttributeSyntaxDefinition(def); 398 s.add(as); 399 m.put(StaticUtils.toLowerCase(as.getOID()), as); 400 } 401 catch (final LDAPException le) 402 { 403 Debug.debugException(le); 404 if (unparsableAttributeSyntaxes != null) 405 { 406 unparsableAttributeSyntaxes.put(def, le); 407 } 408 } 409 } 410 411 asMap = Collections.unmodifiableMap(m); 412 asSet = Collections.unmodifiableSet(s); 413 } 414 415 416 // Decode the attribute types from the schema entry. 417 defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_TYPE); 418 if (defs == null) 419 { 420 atMap = Collections.emptyMap(); 421 atSet = Collections.emptySet(); 422 operationalATSet = Collections.emptySet(); 423 userATSet = Collections.emptySet(); 424 } 425 else 426 { 427 final LinkedHashMap<String,AttributeTypeDefinition> m = 428 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length)); 429 final LinkedHashSet<AttributeTypeDefinition> s = 430 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 431 final LinkedHashSet<AttributeTypeDefinition> sUser = 432 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 433 final LinkedHashSet<AttributeTypeDefinition> sOperational = 434 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 435 436 for (final String def : defs) 437 { 438 try 439 { 440 final AttributeTypeDefinition at = new AttributeTypeDefinition(def); 441 s.add(at); 442 m.put(StaticUtils.toLowerCase(at.getOID()), at); 443 for (final String name : at.getNames()) 444 { 445 m.put(StaticUtils.toLowerCase(name), at); 446 } 447 448 if (at.isOperational()) 449 { 450 sOperational.add(at); 451 } 452 else 453 { 454 sUser.add(at); 455 } 456 } 457 catch (final LDAPException le) 458 { 459 Debug.debugException(le); 460 if (unparsableAttributeTypes != null) 461 { 462 unparsableAttributeTypes.put(def, le); 463 } 464 } 465 } 466 467 atMap = Collections.unmodifiableMap(m); 468 atSet = Collections.unmodifiableSet(s); 469 operationalATSet = Collections.unmodifiableSet(sOperational); 470 userATSet = Collections.unmodifiableSet(sUser); 471 } 472 473 474 // Decode the DIT content rules from the schema entry. 475 defs = schemaEntry.getAttributeValues(ATTR_DIT_CONTENT_RULE); 476 if (defs == null) 477 { 478 dcrMap = Collections.emptyMap(); 479 dcrSet = Collections.emptySet(); 480 } 481 else 482 { 483 final LinkedHashMap<String,DITContentRuleDefinition> m = 484 new LinkedHashMap<>(2*defs.length); 485 final LinkedHashSet<DITContentRuleDefinition> s = 486 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 487 488 for (final String def : defs) 489 { 490 try 491 { 492 final DITContentRuleDefinition dcr = 493 new DITContentRuleDefinition(def); 494 s.add(dcr); 495 m.put(StaticUtils.toLowerCase(dcr.getOID()), dcr); 496 for (final String name : dcr.getNames()) 497 { 498 m.put(StaticUtils.toLowerCase(name), dcr); 499 } 500 } 501 catch (final LDAPException le) 502 { 503 Debug.debugException(le); 504 if (unparsableDITContentRules != null) 505 { 506 unparsableDITContentRules.put(def, le); 507 } 508 } 509 } 510 511 dcrMap = Collections.unmodifiableMap(m); 512 dcrSet = Collections.unmodifiableSet(s); 513 } 514 515 516 // Decode the DIT structure rules from the schema entry. 517 defs = schemaEntry.getAttributeValues(ATTR_DIT_STRUCTURE_RULE); 518 if (defs == null) 519 { 520 dsrMapByID = Collections.emptyMap(); 521 dsrMapByName = Collections.emptyMap(); 522 dsrMapByNameForm = Collections.emptyMap(); 523 dsrSet = Collections.emptySet(); 524 } 525 else 526 { 527 final LinkedHashMap<Integer,DITStructureRuleDefinition> mID = 528 new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length)); 529 final LinkedHashMap<String,DITStructureRuleDefinition> mN = 530 new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length)); 531 final LinkedHashMap<String,DITStructureRuleDefinition> mNF = 532 new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length)); 533 final LinkedHashSet<DITStructureRuleDefinition> s = 534 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 535 536 for (final String def : defs) 537 { 538 try 539 { 540 final DITStructureRuleDefinition dsr = 541 new DITStructureRuleDefinition(def); 542 s.add(dsr); 543 mID.put(dsr.getRuleID(), dsr); 544 mNF.put(StaticUtils.toLowerCase(dsr.getNameFormID()), dsr); 545 for (final String name : dsr.getNames()) 546 { 547 mN.put(StaticUtils.toLowerCase(name), dsr); 548 } 549 } 550 catch (final LDAPException le) 551 { 552 Debug.debugException(le); 553 if (unparsableDITStructureRules != null) 554 { 555 unparsableDITStructureRules.put(def, le); 556 } 557 } 558 } 559 560 dsrMapByID = Collections.unmodifiableMap(mID); 561 dsrMapByName = Collections.unmodifiableMap(mN); 562 dsrMapByNameForm = Collections.unmodifiableMap(mNF); 563 dsrSet = Collections.unmodifiableSet(s); 564 } 565 566 567 // Decode the matching rules from the schema entry. 568 defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE); 569 if (defs == null) 570 { 571 mrMap = Collections.emptyMap(); 572 mrSet = Collections.emptySet(); 573 } 574 else 575 { 576 final LinkedHashMap<String,MatchingRuleDefinition> m = 577 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length)); 578 final LinkedHashSet<MatchingRuleDefinition> s = 579 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 580 581 for (final String def : defs) 582 { 583 try 584 { 585 final MatchingRuleDefinition mr = new MatchingRuleDefinition(def); 586 s.add(mr); 587 m.put(StaticUtils.toLowerCase(mr.getOID()), mr); 588 for (final String name : mr.getNames()) 589 { 590 m.put(StaticUtils.toLowerCase(name), mr); 591 } 592 } 593 catch (final LDAPException le) 594 { 595 Debug.debugException(le); 596 if (unparsableMatchingRules != null) 597 { 598 unparsableMatchingRules.put(def, le); 599 } 600 } 601 } 602 603 mrMap = Collections.unmodifiableMap(m); 604 mrSet = Collections.unmodifiableSet(s); 605 } 606 607 608 // Decode the matching rule uses from the schema entry. 609 defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE_USE); 610 if (defs == null) 611 { 612 mruMap = Collections.emptyMap(); 613 mruSet = Collections.emptySet(); 614 } 615 else 616 { 617 final LinkedHashMap<String,MatchingRuleUseDefinition> m = 618 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length)); 619 final LinkedHashSet<MatchingRuleUseDefinition> s = 620 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 621 622 for (final String def : defs) 623 { 624 try 625 { 626 final MatchingRuleUseDefinition mru = 627 new MatchingRuleUseDefinition(def); 628 s.add(mru); 629 m.put(StaticUtils.toLowerCase(mru.getOID()), mru); 630 for (final String name : mru.getNames()) 631 { 632 m.put(StaticUtils.toLowerCase(name), mru); 633 } 634 } 635 catch (final LDAPException le) 636 { 637 Debug.debugException(le); 638 if (unparsableMatchingRuleUses != null) 639 { 640 unparsableMatchingRuleUses.put(def, le); 641 } 642 } 643 } 644 645 mruMap = Collections.unmodifiableMap(m); 646 mruSet = Collections.unmodifiableSet(s); 647 } 648 649 650 // Decode the name forms from the schema entry. 651 defs = schemaEntry.getAttributeValues(ATTR_NAME_FORM); 652 if (defs == null) 653 { 654 nfMapByName = Collections.emptyMap(); 655 nfMapByOC = Collections.emptyMap(); 656 nfSet = Collections.emptySet(); 657 } 658 else 659 { 660 final LinkedHashMap<String,NameFormDefinition> mN = 661 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length)); 662 final LinkedHashMap<String,NameFormDefinition> mOC = 663 new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length)); 664 final LinkedHashSet<NameFormDefinition> s = 665 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 666 667 for (final String def : defs) 668 { 669 try 670 { 671 final NameFormDefinition nf = new NameFormDefinition(def); 672 s.add(nf); 673 mOC.put(StaticUtils.toLowerCase(nf.getStructuralClass()), nf); 674 mN.put(StaticUtils.toLowerCase(nf.getOID()), nf); 675 for (final String name : nf.getNames()) 676 { 677 mN.put(StaticUtils.toLowerCase(name), nf); 678 } 679 } 680 catch (final LDAPException le) 681 { 682 Debug.debugException(le); 683 if(unparsableNameForms != null) 684 { 685 unparsableNameForms.put(def, le); 686 } 687 } 688 } 689 690 nfMapByName = Collections.unmodifiableMap(mN); 691 nfMapByOC = Collections.unmodifiableMap(mOC); 692 nfSet = Collections.unmodifiableSet(s); 693 } 694 695 696 // Decode the object classes from the schema entry. 697 defs = schemaEntry.getAttributeValues(ATTR_OBJECT_CLASS); 698 if (defs == null) 699 { 700 ocMap = Collections.emptyMap(); 701 ocSet = Collections.emptySet(); 702 abstractOCSet = Collections.emptySet(); 703 auxiliaryOCSet = Collections.emptySet(); 704 structuralOCSet = Collections.emptySet(); 705 } 706 else 707 { 708 final LinkedHashMap<String,ObjectClassDefinition> m = 709 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length)); 710 final LinkedHashSet<ObjectClassDefinition> s = 711 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 712 final LinkedHashSet<ObjectClassDefinition> sAbstract = 713 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 714 final LinkedHashSet<ObjectClassDefinition> sAuxiliary = 715 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 716 final LinkedHashSet<ObjectClassDefinition> sStructural = 717 new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length)); 718 719 for (final String def : defs) 720 { 721 try 722 { 723 final ObjectClassDefinition oc = new ObjectClassDefinition(def); 724 s.add(oc); 725 m.put(StaticUtils.toLowerCase(oc.getOID()), oc); 726 for (final String name : oc.getNames()) 727 { 728 m.put(StaticUtils.toLowerCase(name), oc); 729 } 730 731 switch (oc.getObjectClassType(this)) 732 { 733 case ABSTRACT: 734 sAbstract.add(oc); 735 break; 736 case AUXILIARY: 737 sAuxiliary.add(oc); 738 break; 739 case STRUCTURAL: 740 sStructural.add(oc); 741 break; 742 } 743 } 744 catch (final LDAPException le) 745 { 746 Debug.debugException(le); 747 if (unparsableObjectClasses != null) 748 { 749 unparsableObjectClasses.put(def, le); 750 } 751 } 752 } 753 754 ocMap = Collections.unmodifiableMap(m); 755 ocSet = Collections.unmodifiableSet(s); 756 abstractOCSet = Collections.unmodifiableSet(sAbstract); 757 auxiliaryOCSet = Collections.unmodifiableSet(sAuxiliary); 758 structuralOCSet = Collections.unmodifiableSet(sStructural); 759 } 760 761 762 // Populate the map of subordinate attribute types. 763 final LinkedHashMap<AttributeTypeDefinition,List<AttributeTypeDefinition>> 764 subAttrTypes = new LinkedHashMap<>( 765 StaticUtils.computeMapCapacity(atSet.size())); 766 for (final AttributeTypeDefinition d : atSet) 767 { 768 AttributeTypeDefinition sup = d.getSuperiorType(this); 769 while (sup != null) 770 { 771 List<AttributeTypeDefinition> l = subAttrTypes.get(sup); 772 if (l == null) 773 { 774 l = new ArrayList<>(1); 775 subAttrTypes.put(sup, l); 776 } 777 l.add(d); 778 779 sup = sup.getSuperiorType(this); 780 } 781 } 782 subordinateAttributeTypes = Collections.unmodifiableMap(subAttrTypes); 783 } 784 785 786 787 /** 788 * Parses all schema elements contained in the provided entry. This method 789 * differs from the {@link #Schema(Entry)} constructor in that this method 790 * will throw an exception if it encounters any unparsable schema elements, 791 * while the constructor will silently ignore them. Alternately, the 792 * 'constructor that takes a bunch of maps can be used to 793 * 794 * @param schemaEntry The schema entry to parse. It must not be 795 * {@code null}. 796 * 797 * @return The schema entry that was parsed. 798 * 799 * @throws LDAPException If the provided entry contains any schema element 800 * definitions that cannot be parsed. 801 */ 802 @NotNull() 803 public static Schema parseSchemaEntry(@NotNull final Entry schemaEntry) 804 throws LDAPException 805 { 806 final Map<String,LDAPException> unparsableAttributeSyntaxes = 807 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 808 final Map<String,LDAPException> unparsableMatchingRules = 809 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 810 final Map<String,LDAPException> unparsableAttributeTypes = 811 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 812 final Map<String,LDAPException> unparsableObjectClasses = 813 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 814 final Map<String,LDAPException> unparsableDITContentRules = 815 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 816 final Map<String,LDAPException> unparsableDITStructureRules = 817 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 818 final Map<String,LDAPException> unparsableNameForms = 819 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 820 final Map<String,LDAPException> unparsableMatchingRuleUses = 821 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 822 823 final Schema schema = new Schema(schemaEntry, unparsableAttributeSyntaxes, 824 unparsableMatchingRules, unparsableAttributeTypes, 825 unparsableObjectClasses, unparsableDITContentRules, 826 unparsableDITStructureRules, unparsableNameForms, 827 unparsableMatchingRuleUses); 828 if (unparsableAttributeSyntaxes.isEmpty() && 829 unparsableMatchingRules.isEmpty() && 830 unparsableAttributeTypes.isEmpty() && 831 unparsableObjectClasses.isEmpty() && 832 unparsableDITContentRules.isEmpty() && 833 unparsableDITStructureRules.isEmpty() && 834 unparsableNameForms.isEmpty() && 835 unparsableMatchingRuleUses.isEmpty()) 836 { 837 return schema; 838 } 839 840 final StringBuilder messageBuffer = new StringBuilder(); 841 for (final Map.Entry<String,LDAPException> e : 842 unparsableAttributeSyntaxes.entrySet()) 843 { 844 appendErrorMessage(messageBuffer, 845 ERR_SCHEMA_UNPARSABLE_AS.get(ATTR_ATTRIBUTE_SYNTAX, e.getKey(), 846 StaticUtils.getExceptionMessage(e.getValue()))); 847 } 848 849 for (final Map.Entry<String,LDAPException> e : 850 unparsableMatchingRules.entrySet()) 851 { 852 appendErrorMessage(messageBuffer, 853 ERR_SCHEMA_UNPARSABLE_MR.get(ATTR_MATCHING_RULE, e.getKey(), 854 StaticUtils.getExceptionMessage(e.getValue()))); 855 } 856 857 for (final Map.Entry<String,LDAPException> e : 858 unparsableAttributeTypes.entrySet()) 859 { 860 appendErrorMessage(messageBuffer, 861 ERR_SCHEMA_UNPARSABLE_AT.get(ATTR_ATTRIBUTE_TYPE, e.getKey(), 862 StaticUtils.getExceptionMessage(e.getValue()))); 863 } 864 865 for (final Map.Entry<String,LDAPException> e : 866 unparsableObjectClasses.entrySet()) 867 { 868 appendErrorMessage(messageBuffer, 869 ERR_SCHEMA_UNPARSABLE_OC.get(ATTR_OBJECT_CLASS, e.getKey(), 870 StaticUtils.getExceptionMessage(e.getValue()))); 871 } 872 873 for (final Map.Entry<String,LDAPException> e : 874 unparsableDITContentRules.entrySet()) 875 { 876 appendErrorMessage(messageBuffer, 877 ERR_SCHEMA_UNPARSABLE_DCR.get(ATTR_DIT_CONTENT_RULE, e.getKey(), 878 StaticUtils.getExceptionMessage(e.getValue()))); 879 } 880 881 for (final Map.Entry<String,LDAPException> e : 882 unparsableDITStructureRules.entrySet()) 883 { 884 appendErrorMessage(messageBuffer, 885 ERR_SCHEMA_UNPARSABLE_DSR.get(ATTR_DIT_STRUCTURE_RULE, e.getKey(), 886 StaticUtils.getExceptionMessage(e.getValue()))); 887 } 888 889 for (final Map.Entry<String,LDAPException> e : 890 unparsableNameForms.entrySet()) 891 { 892 appendErrorMessage(messageBuffer, 893 ERR_SCHEMA_UNPARSABLE_NF.get(ATTR_NAME_FORM, e.getKey(), 894 StaticUtils.getExceptionMessage(e.getValue()))); 895 } 896 897 for (final Map.Entry<String,LDAPException> e : 898 unparsableMatchingRuleUses.entrySet()) 899 { 900 appendErrorMessage(messageBuffer, 901 ERR_SCHEMA_UNPARSABLE_MRU.get(ATTR_MATCHING_RULE_USE, e.getKey(), 902 StaticUtils.getExceptionMessage(e.getValue()))); 903 } 904 905 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 906 messageBuffer.toString()); 907 } 908 909 910 911 /** 912 * Appends the provided message to the given buffer, adding spaces and 913 * punctuation if necessary. 914 * 915 * @param buffer The buffer to which the message should be appended. 916 * @param message The message to append to the buffer. 917 */ 918 private static void appendErrorMessage(@NotNull final StringBuilder buffer, 919 @NotNull final String message) 920 { 921 final int length = buffer.length(); 922 if (length > 0) 923 { 924 if (buffer.charAt(length - 1) == '.') 925 { 926 buffer.append(" "); 927 } 928 else 929 { 930 buffer.append(". "); 931 } 932 } 933 934 buffer.append(message); 935 } 936 937 938 939 /** 940 * Retrieves the directory server schema over the provided connection. The 941 * root DSE will first be retrieved in order to get its subschemaSubentry DN, 942 * and then that entry will be retrieved from the server and its contents 943 * decoded as schema elements. This should be sufficient for directories that 944 * only provide a single schema, but for directories with multiple schemas it 945 * may be necessary to specify the DN of an entry for which to retrieve the 946 * subschema subentry. Any unparsable schema elements will be silently 947 * ignored. 948 * 949 * @param connection The connection to use in order to retrieve the server 950 * schema. It must not be {@code null}. 951 * 952 * @return A decoded representation of the server schema. 953 * 954 * @throws LDAPException If a problem occurs while obtaining the server 955 * schema. 956 */ 957 @Nullable() 958 public static Schema getSchema(@NotNull final LDAPConnection connection) 959 throws LDAPException 960 { 961 return getSchema(connection, ""); 962 } 963 964 965 966 /** 967 * Retrieves the directory server schema that governs the specified entry. 968 * In some servers, different portions of the DIT may be served by different 969 * schemas, and in such cases it will be necessary to provide the DN of the 970 * target entry in order to ensure that the appropriate schema which governs 971 * that entry is returned. For servers that support only a single schema, 972 * any entry DN (including that of the root DSE) should be sufficient. Any 973 * unparsable schema elements will be silently ignored. 974 * 975 * @param connection The connection to use in order to retrieve the server 976 * schema. It must not be {@code null}. 977 * @param entryDN The DN of the entry for which to retrieve the governing 978 * schema. It may be {@code null} or an empty string in 979 * order to retrieve the schema that governs the server's 980 * root DSE. 981 * 982 * @return A decoded representation of the server schema, or {@code null} if 983 * it is not available for some reason (e.g., the client does not 984 * have permission to read the server schema). 985 * 986 * @throws LDAPException If a problem occurs while obtaining the server 987 * schema. 988 */ 989 @Nullable() 990 public static Schema getSchema(@NotNull final LDAPConnection connection, 991 @Nullable final String entryDN) 992 throws LDAPException 993 { 994 return getSchema(connection, entryDN, false); 995 } 996 997 998 999 /** 1000 * Retrieves the directory server schema that governs the specified entry. 1001 * In some servers, different portions of the DIT may be served by different 1002 * schemas, and in such cases it will be necessary to provide the DN of the 1003 * target entry in order to ensure that the appropriate schema which governs 1004 * that entry is returned. For servers that support only a single schema, 1005 * any entry DN (including that of the root DSE) should be sufficient. This 1006 * method may optionally throw an exception if the retrieved schema contains 1007 * one or more unparsable schema elements. 1008 * 1009 * @param connection The connection to use in order to 1010 * retrieve the server schema. It must not 1011 * be {@code null}. 1012 * @param entryDN The DN of the entry for which to retrieve 1013 * the governing schema. It may be 1014 * {@code null} or an empty string in order 1015 * to retrieve the schema that governs the 1016 * server's root DSE. 1017 * @param throwOnUnparsableElement Indicates whether to throw an exception 1018 * if the schema entry that is retrieved has 1019 * one or more unparsable schema elements. 1020 * 1021 * @return A decoded representation of the server schema, or {@code null} if 1022 * it is not available for some reason (e.g., the client does not 1023 * have permission to read the server schema). 1024 * 1025 * @throws LDAPException If a problem occurs while obtaining the server 1026 * schema, or if the schema contains one or more 1027 * unparsable elements and 1028 * {@code throwOnUnparsableElement} is {@code true}. 1029 */ 1030 @Nullable() 1031 public static Schema getSchema(@NotNull final LDAPConnection connection, 1032 @Nullable final String entryDN, 1033 final boolean throwOnUnparsableElement) 1034 throws LDAPException 1035 { 1036 Validator.ensureNotNull(connection); 1037 1038 final String subschemaSubentryDN; 1039 if (entryDN == null) 1040 { 1041 subschemaSubentryDN = getSubschemaSubentryDN(connection, ""); 1042 } 1043 else 1044 { 1045 subschemaSubentryDN = getSubschemaSubentryDN(connection, entryDN); 1046 } 1047 1048 if (subschemaSubentryDN == null) 1049 { 1050 return null; 1051 } 1052 1053 final Entry schemaEntry = connection.searchForEntry(subschemaSubentryDN, 1054 SearchScope.BASE, 1055 Filter.createEqualityFilter("objectClass", "subschema"), 1056 SCHEMA_REQUEST_ATTRS); 1057 if (schemaEntry == null) 1058 { 1059 return null; 1060 } 1061 1062 if (throwOnUnparsableElement) 1063 { 1064 return parseSchemaEntry(schemaEntry); 1065 } 1066 else 1067 { 1068 return new Schema(schemaEntry); 1069 } 1070 } 1071 1072 1073 1074 /** 1075 * Reads schema information from one or more files containing the schema 1076 * represented in LDIF form, with the definitions represented in the form 1077 * described in section 4.1 of RFC 4512. Each file should contain a single 1078 * entry. Any unparsable schema elements will be silently ignored. 1079 * 1080 * @param schemaFiles The paths to the LDIF files containing the schema 1081 * information to be read. At least one file must be 1082 * specified. If multiple files are specified, then they 1083 * will be processed in the order in which they have been 1084 * listed. 1085 * 1086 * @return The schema read from the specified schema files, or {@code null} 1087 * if none of the files contains any LDIF data to be read. 1088 * 1089 * @throws IOException If a problem occurs while attempting to read from 1090 * any of the specified files. 1091 * 1092 * @throws LDIFException If a problem occurs while attempting to parse the 1093 * contents of any of the schema files. 1094 */ 1095 @Nullable() 1096 public static Schema getSchema(@NotNull final String... schemaFiles) 1097 throws IOException, LDIFException 1098 { 1099 Validator.ensureNotNull(schemaFiles); 1100 Validator.ensureFalse(schemaFiles.length == 0); 1101 1102 final ArrayList<File> files = new ArrayList<>(schemaFiles.length); 1103 for (final String s : schemaFiles) 1104 { 1105 files.add(new File(s)); 1106 } 1107 1108 return getSchema(files); 1109 } 1110 1111 1112 1113 /** 1114 * Reads schema information from one or more files containing the schema 1115 * represented in LDIF form, with the definitions represented in the form 1116 * described in section 4.1 of RFC 4512. Each file should contain a single 1117 * entry. Any unparsable schema elements will be silently ignored. 1118 * 1119 * @param schemaFiles The paths to the LDIF files containing the schema 1120 * information to be read. At least one file must be 1121 * specified. If multiple files are specified, then they 1122 * will be processed in the order in which they have been 1123 * listed. 1124 * 1125 * @return The schema read from the specified schema files, or {@code null} 1126 * if none of the files contains any LDIF data to be read. 1127 * 1128 * @throws IOException If a problem occurs while attempting to read from 1129 * any of the specified files. 1130 * 1131 * @throws LDIFException If a problem occurs while attempting to parse the 1132 * contents of any of the schema files. 1133 */ 1134 @Nullable() 1135 public static Schema getSchema(@NotNull final File... schemaFiles) 1136 throws IOException, LDIFException 1137 { 1138 Validator.ensureNotNull(schemaFiles); 1139 Validator.ensureFalse(schemaFiles.length == 0); 1140 1141 return getSchema(Arrays.asList(schemaFiles)); 1142 } 1143 1144 1145 1146 /** 1147 * Reads schema information from one or more files containing the schema 1148 * represented in LDIF form, with the definitions represented in the form 1149 * described in section 4.1 of RFC 4512. Each file should contain a single 1150 * entry. Any unparsable schema elements will be silently ignored. 1151 * 1152 * @param schemaFiles The paths to the LDIF files containing the schema 1153 * information to be read. At least one file must be 1154 * specified. If multiple files are specified, then they 1155 * will be processed in the order in which they have been 1156 * listed. 1157 * 1158 * @return The schema read from the specified schema files, or {@code null} 1159 * if none of the files contains any LDIF data to be read. 1160 * 1161 * @throws IOException If a problem occurs while attempting to read from 1162 * any of the specified files. 1163 * 1164 * @throws LDIFException If a problem occurs while attempting to parse the 1165 * contents of any of the schema files. 1166 */ 1167 @Nullable() 1168 public static Schema getSchema(@NotNull final List<File> schemaFiles) 1169 throws IOException, LDIFException 1170 { 1171 return getSchema(schemaFiles, false); 1172 } 1173 1174 1175 1176 /** 1177 * Reads schema information from one or more files containing the schema 1178 * represented in LDIF form, with the definitions represented in the form 1179 * described in section 4.1 of RFC 4512. Each file should contain a single 1180 * entry. 1181 * 1182 * @param schemaFiles The paths to the LDIF files containing 1183 * the schema information to be read. At 1184 * least one file must be specified. If 1185 * multiple files are specified, then they 1186 * will be processed in the order in which 1187 * they have been listed. 1188 * @param throwOnUnparsableElement Indicates whether to throw an exception 1189 * if the schema entry that is retrieved has 1190 * one or more unparsable schema elements. 1191 * 1192 * @return The schema read from the specified schema files, or {@code null} 1193 * if none of the files contains any LDIF data to be read. 1194 * 1195 * @throws IOException If a problem occurs while attempting to read from 1196 * any of the specified files. 1197 * 1198 * @throws LDIFException If a problem occurs while attempting to parse the 1199 * contents of any of the schema files. If 1200 * {@code throwOnUnparsableElement} is {@code true}, 1201 * then this may also be thrown if any of the schema 1202 * files contains any unparsable schema elements. 1203 */ 1204 @Nullable() 1205 public static Schema getSchema(@NotNull final List<File> schemaFiles, 1206 final boolean throwOnUnparsableElement) 1207 throws IOException, LDIFException 1208 { 1209 Validator.ensureNotNull(schemaFiles); 1210 Validator.ensureFalse(schemaFiles.isEmpty()); 1211 1212 Entry schemaEntry = null; 1213 for (final File f : schemaFiles) 1214 { 1215 final LDIFReader ldifReader = new LDIFReader(f); 1216 1217 try 1218 { 1219 final Entry e = ldifReader.readEntry(); 1220 if (e == null) 1221 { 1222 continue; 1223 } 1224 1225 e.addAttribute("objectClass", "top", "ldapSubentry", "subschema"); 1226 1227 if (schemaEntry == null) 1228 { 1229 schemaEntry = e; 1230 } 1231 else 1232 { 1233 for (final Attribute a : e.getAttributes()) 1234 { 1235 schemaEntry.addAttribute(a); 1236 } 1237 } 1238 } 1239 finally 1240 { 1241 ldifReader.close(); 1242 } 1243 } 1244 1245 if (schemaEntry == null) 1246 { 1247 return null; 1248 } 1249 1250 if (throwOnUnparsableElement) 1251 { 1252 try 1253 { 1254 return parseSchemaEntry(schemaEntry); 1255 } 1256 catch (final LDAPException e) 1257 { 1258 Debug.debugException(e); 1259 throw new LDIFException(e.getMessage(), 0, false, e); 1260 } 1261 } 1262 else 1263 { 1264 return new Schema(schemaEntry); 1265 } 1266 } 1267 1268 1269 1270 /** 1271 * Reads schema information from the provided input stream. The information 1272 * should be in LDIF form, with the definitions represented in the form 1273 * described in section 4.1 of RFC 4512. Only a single entry will be read 1274 * from the input stream, and it will be closed at the end of this method. 1275 * 1276 * @param inputStream The input stream from which the schema entry will be 1277 * read. It must not be {@code null}, and it will be 1278 * closed when this method returns. 1279 * 1280 * @return The schema read from the provided input stream, or {@code null} if 1281 * the end of the input stream is reached without reading any data. 1282 * 1283 * @throws IOException If a problem is encountered while attempting to read 1284 * from the provided input stream. 1285 * 1286 * @throws LDIFException If a problem occurs while attempting to parse the 1287 * data read as LDIF. 1288 */ 1289 @Nullable() 1290 public static Schema getSchema(@NotNull final InputStream inputStream) 1291 throws IOException, LDIFException 1292 { 1293 Validator.ensureNotNull(inputStream); 1294 1295 final LDIFReader ldifReader = new LDIFReader(inputStream); 1296 1297 try 1298 { 1299 final Entry e = ldifReader.readEntry(); 1300 if (e == null) 1301 { 1302 return null; 1303 } 1304 else 1305 { 1306 return new Schema(e); 1307 } 1308 } 1309 finally 1310 { 1311 ldifReader.close(); 1312 } 1313 } 1314 1315 1316 1317 /** 1318 * Retrieves a schema object that contains definitions for a number of 1319 * standard attribute types and object classes from LDAP-related RFCs and 1320 * Internet Drafts. 1321 * 1322 * @return A schema object that contains definitions for a number of standard 1323 * attribute types and object classes from LDAP-related RFCs and 1324 * Internet Drafts. 1325 * 1326 * @throws LDAPException If a problem occurs while attempting to obtain or 1327 * parse the default standard schema definitions. 1328 */ 1329 @NotNull() 1330 public static Schema getDefaultStandardSchema() 1331 throws LDAPException 1332 { 1333 final Schema s = DEFAULT_STANDARD_SCHEMA.get(); 1334 if (s != null) 1335 { 1336 return s; 1337 } 1338 1339 synchronized (DEFAULT_STANDARD_SCHEMA) 1340 { 1341 try 1342 { 1343 final ClassLoader classLoader = Schema.class.getClassLoader(); 1344 final InputStream inputStream = 1345 classLoader.getResourceAsStream(DEFAULT_SCHEMA_RESOURCE_PATH); 1346 final LDIFReader ldifReader = new LDIFReader(inputStream); 1347 final Entry schemaEntry = ldifReader.readEntry(); 1348 ldifReader.close(); 1349 1350 final Schema schema = new Schema(schemaEntry); 1351 DEFAULT_STANDARD_SCHEMA.set(schema); 1352 return schema; 1353 } 1354 catch (final Exception e) 1355 { 1356 Debug.debugException(e); 1357 throw new LDAPException(ResultCode.LOCAL_ERROR, 1358 ERR_SCHEMA_CANNOT_LOAD_DEFAULT_DEFINITIONS.get( 1359 StaticUtils.getExceptionMessage(e)), 1360 e); 1361 } 1362 } 1363 } 1364 1365 1366 1367 /** 1368 * Retrieves a schema containing all of the elements of each of the provided 1369 * schemas. 1370 * 1371 * @param schemas The schemas to be merged. It must not be {@code null} or 1372 * empty. 1373 * 1374 * @return A merged representation of the provided schemas. 1375 */ 1376 @Nullable() 1377 public static Schema mergeSchemas(@NotNull final Schema... schemas) 1378 { 1379 if ((schemas == null) || (schemas.length == 0)) 1380 { 1381 return null; 1382 } 1383 else if (schemas.length == 1) 1384 { 1385 return schemas[0]; 1386 } 1387 1388 final LinkedHashMap<String,String> asMap = 1389 new LinkedHashMap<>(StaticUtils.computeMapCapacity(100)); 1390 final LinkedHashMap<String,String> atMap = 1391 new LinkedHashMap<>(StaticUtils.computeMapCapacity(100)); 1392 final LinkedHashMap<String,String> dcrMap = 1393 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 1394 final LinkedHashMap<Integer,String> dsrMap = 1395 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 1396 final LinkedHashMap<String,String> mrMap = 1397 new LinkedHashMap<>(StaticUtils.computeMapCapacity(100)); 1398 final LinkedHashMap<String,String> mruMap = 1399 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 1400 final LinkedHashMap<String,String> nfMap = 1401 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 1402 final LinkedHashMap<String,String> ocMap = 1403 new LinkedHashMap<>(StaticUtils.computeMapCapacity(100)); 1404 1405 for (final Schema s : schemas) 1406 { 1407 for (final AttributeSyntaxDefinition as : s.asSet) 1408 { 1409 asMap.put(StaticUtils.toLowerCase(as.getOID()), as.toString()); 1410 } 1411 1412 for (final AttributeTypeDefinition at : s.atSet) 1413 { 1414 atMap.put(StaticUtils.toLowerCase(at.getOID()), at.toString()); 1415 } 1416 1417 for (final DITContentRuleDefinition dcr : s.dcrSet) 1418 { 1419 dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr.toString()); 1420 } 1421 1422 for (final DITStructureRuleDefinition dsr : s.dsrSet) 1423 { 1424 dsrMap.put(dsr.getRuleID(), dsr.toString()); 1425 } 1426 1427 for (final MatchingRuleDefinition mr : s.mrSet) 1428 { 1429 mrMap.put(StaticUtils.toLowerCase(mr.getOID()), mr.toString()); 1430 } 1431 1432 for (final MatchingRuleUseDefinition mru : s.mruSet) 1433 { 1434 mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru.toString()); 1435 } 1436 1437 for (final NameFormDefinition nf : s.nfSet) 1438 { 1439 nfMap.put(StaticUtils.toLowerCase(nf.getOID()), nf.toString()); 1440 } 1441 1442 for (final ObjectClassDefinition oc : s.ocSet) 1443 { 1444 ocMap.put(StaticUtils.toLowerCase(oc.getOID()), oc.toString()); 1445 } 1446 } 1447 1448 final Entry e = new Entry(schemas[0].getSchemaEntry().getDN()); 1449 1450 final Attribute ocAttr = 1451 schemas[0].getSchemaEntry().getObjectClassAttribute(); 1452 if (ocAttr == null) 1453 { 1454 e.addAttribute("objectClass", "top", "ldapSubEntry", "subschema"); 1455 } 1456 else 1457 { 1458 e.addAttribute(ocAttr); 1459 } 1460 1461 if (! asMap.isEmpty()) 1462 { 1463 final String[] values = new String[asMap.size()]; 1464 e.addAttribute(ATTR_ATTRIBUTE_SYNTAX, asMap.values().toArray(values)); 1465 } 1466 1467 if (! mrMap.isEmpty()) 1468 { 1469 final String[] values = new String[mrMap.size()]; 1470 e.addAttribute(ATTR_MATCHING_RULE, mrMap.values().toArray(values)); 1471 } 1472 1473 if (! atMap.isEmpty()) 1474 { 1475 final String[] values = new String[atMap.size()]; 1476 e.addAttribute(ATTR_ATTRIBUTE_TYPE, atMap.values().toArray(values)); 1477 } 1478 1479 if (! ocMap.isEmpty()) 1480 { 1481 final String[] values = new String[ocMap.size()]; 1482 e.addAttribute(ATTR_OBJECT_CLASS, ocMap.values().toArray(values)); 1483 } 1484 1485 if (! dcrMap.isEmpty()) 1486 { 1487 final String[] values = new String[dcrMap.size()]; 1488 e.addAttribute(ATTR_DIT_CONTENT_RULE, dcrMap.values().toArray(values)); 1489 } 1490 1491 if (! dsrMap.isEmpty()) 1492 { 1493 final String[] values = new String[dsrMap.size()]; 1494 e.addAttribute(ATTR_DIT_STRUCTURE_RULE, dsrMap.values().toArray(values)); 1495 } 1496 1497 if (! nfMap.isEmpty()) 1498 { 1499 final String[] values = new String[nfMap.size()]; 1500 e.addAttribute(ATTR_NAME_FORM, nfMap.values().toArray(values)); 1501 } 1502 1503 if (! mruMap.isEmpty()) 1504 { 1505 final String[] values = new String[mruMap.size()]; 1506 e.addAttribute(ATTR_MATCHING_RULE_USE, mruMap.values().toArray(values)); 1507 } 1508 1509 return new Schema(e); 1510 } 1511 1512 1513 1514 /** 1515 * Retrieves the entry used to create this schema object. 1516 * 1517 * @return The entry used to create this schema object. 1518 */ 1519 @NotNull() 1520 public ReadOnlyEntry getSchemaEntry() 1521 { 1522 return schemaEntry; 1523 } 1524 1525 1526 1527 /** 1528 * Retrieves the value of the subschemaSubentry attribute from the specified 1529 * entry using the provided connection. 1530 * 1531 * @param connection The connection to use in order to perform the search. 1532 * It must not be {@code null}. 1533 * @param entryDN The DN of the entry from which to retrieve the 1534 * subschemaSubentry attribute. It may be {@code null} or 1535 * an empty string in order to retrieve the value from the 1536 * server's root DSE. 1537 * 1538 * @return The value of the subschemaSubentry attribute from the specified 1539 * entry, or {@code null} if it is not available for some reason 1540 * (e.g., the client does not have permission to read the target 1541 * entry or the subschemaSubentry attribute). 1542 * 1543 * @throws LDAPException If a problem occurs while attempting to retrieve 1544 * the specified entry. 1545 */ 1546 @Nullable() 1547 public static String getSubschemaSubentryDN( 1548 @NotNull final LDAPConnection connection, 1549 @Nullable final String entryDN) 1550 throws LDAPException 1551 { 1552 Validator.ensureNotNull(connection); 1553 1554 final Entry e; 1555 if (entryDN == null) 1556 { 1557 e = connection.getEntry("", SUBSCHEMA_SUBENTRY_REQUEST_ATTRS); 1558 } 1559 else 1560 { 1561 e = connection.getEntry(entryDN, SUBSCHEMA_SUBENTRY_REQUEST_ATTRS); 1562 } 1563 1564 if (e == null) 1565 { 1566 return null; 1567 } 1568 1569 return e.getAttributeValue(ATTR_SUBSCHEMA_SUBENTRY); 1570 } 1571 1572 1573 1574 /** 1575 * Retrieves the set of attribute syntax definitions contained in the server 1576 * schema. 1577 * 1578 * @return The set of attribute syntax definitions contained in the server 1579 * schema. 1580 */ 1581 @NotNull() 1582 public Set<AttributeSyntaxDefinition> getAttributeSyntaxes() 1583 { 1584 return asSet; 1585 } 1586 1587 1588 1589 /** 1590 * Retrieves the attribute syntax with the specified OID from the server 1591 * schema. 1592 * 1593 * @param oid The OID of the attribute syntax to retrieve. It must not be 1594 * {@code null}. It may optionally include a minimum upper bound 1595 * (as may appear when the syntax OID is included in an attribute 1596 * type definition), but if it does then that portion will be 1597 * ignored when retrieving the attribute syntax. 1598 * 1599 * @return The requested attribute syntax, or {@code null} if there is no 1600 * such syntax defined in the server schema. 1601 */ 1602 @Nullable() 1603 public AttributeSyntaxDefinition getAttributeSyntax(@NotNull final String oid) 1604 { 1605 Validator.ensureNotNull(oid); 1606 1607 final String lowerOID = StaticUtils.toLowerCase(oid); 1608 final int curlyPos = lowerOID.indexOf('{'); 1609 1610 if (curlyPos > 0) 1611 { 1612 return asMap.get(lowerOID.substring(0, curlyPos)); 1613 } 1614 else 1615 { 1616 return asMap.get(lowerOID); 1617 } 1618 } 1619 1620 1621 1622 /** 1623 * Retrieves the set of attribute type definitions contained in the server 1624 * schema. 1625 * 1626 * @return The set of attribute type definitions contained in the server 1627 * schema. 1628 */ 1629 @NotNull() 1630 public Set<AttributeTypeDefinition> getAttributeTypes() 1631 { 1632 return atSet; 1633 } 1634 1635 1636 1637 /** 1638 * Retrieves the set of operational attribute type definitions (i.e., those 1639 * definitions with a usage of directoryOperation, distributedOperation, or 1640 * dSAOperation) contained in the server schema. 1641 * 1642 * @return The set of operational attribute type definitions contained in the 1643 * server schema. 1644 */ 1645 @NotNull() 1646 public Set<AttributeTypeDefinition> getOperationalAttributeTypes() 1647 { 1648 return operationalATSet; 1649 } 1650 1651 1652 1653 /** 1654 * Retrieves the set of user attribute type definitions (i.e., those 1655 * definitions with a usage of userApplications) contained in the server 1656 * schema. 1657 * 1658 * @return The set of user attribute type definitions contained in the server 1659 * schema. 1660 */ 1661 @NotNull() 1662 public Set<AttributeTypeDefinition> getUserAttributeTypes() 1663 { 1664 return userATSet; 1665 } 1666 1667 1668 1669 /** 1670 * Retrieves the attribute type with the specified name or OID from the server 1671 * schema. 1672 * 1673 * @param name The name or OID of the attribute type to retrieve. It must 1674 * not be {@code null}. 1675 * 1676 * @return The requested attribute type, or {@code null} if there is no 1677 * such attribute type defined in the server schema. 1678 */ 1679 @Nullable() 1680 public AttributeTypeDefinition getAttributeType(@NotNull final String name) 1681 { 1682 Validator.ensureNotNull(name); 1683 1684 return atMap.get(StaticUtils.toLowerCase(name)); 1685 } 1686 1687 1688 1689 /** 1690 * Retrieves a list of all subordinate attribute type definitions for the 1691 * provided attribute type definition. 1692 * 1693 * @param d The attribute type definition for which to retrieve all 1694 * subordinate attribute types. It must not be {@code null}. 1695 * 1696 * @return A list of all subordinate attribute type definitions for the 1697 * provided attribute type definition, or an empty list if it does 1698 * not have any subordinate types or the provided attribute type is 1699 * not defined in the schema. 1700 */ 1701 @NotNull() 1702 public List<AttributeTypeDefinition> getSubordinateAttributeTypes( 1703 @NotNull final AttributeTypeDefinition d) 1704 { 1705 Validator.ensureNotNull(d); 1706 1707 final List<AttributeTypeDefinition> l = subordinateAttributeTypes.get(d); 1708 if (l == null) 1709 { 1710 return Collections.emptyList(); 1711 } 1712 else 1713 { 1714 return Collections.unmodifiableList(l); 1715 } 1716 } 1717 1718 1719 1720 /** 1721 * Retrieves the set of DIT content rule definitions contained in the server 1722 * schema. 1723 * 1724 * @return The set of DIT content rule definitions contained in the server 1725 * schema. 1726 */ 1727 @NotNull() 1728 public Set<DITContentRuleDefinition> getDITContentRules() 1729 { 1730 return dcrSet; 1731 } 1732 1733 1734 1735 /** 1736 * Retrieves the DIT content rule with the specified name or OID from the 1737 * server schema. 1738 * 1739 * @param name The name or OID of the DIT content rule to retrieve. It must 1740 * not be {@code null}. 1741 * 1742 * @return The requested DIT content rule, or {@code null} if there is no 1743 * such rule defined in the server schema. 1744 */ 1745 @Nullable() 1746 public DITContentRuleDefinition getDITContentRule(@NotNull final String name) 1747 { 1748 Validator.ensureNotNull(name); 1749 1750 return dcrMap.get(StaticUtils.toLowerCase(name)); 1751 } 1752 1753 1754 1755 /** 1756 * Retrieves the set of DIT structure rule definitions contained in the server 1757 * schema. 1758 * 1759 * @return The set of DIT structure rule definitions contained in the server 1760 * schema. 1761 */ 1762 @NotNull() 1763 public Set<DITStructureRuleDefinition> getDITStructureRules() 1764 { 1765 return dsrSet; 1766 } 1767 1768 1769 1770 /** 1771 * Retrieves the DIT content rule with the specified rule ID from the server 1772 * schema. 1773 * 1774 * @param ruleID The rule ID for the DIT structure rule to retrieve. 1775 * 1776 * @return The requested DIT structure rule, or {@code null} if there is no 1777 * such rule defined in the server schema. 1778 */ 1779 @Nullable() 1780 public DITStructureRuleDefinition getDITStructureRuleByID(final int ruleID) 1781 { 1782 return dsrMapByID.get(ruleID); 1783 } 1784 1785 1786 1787 /** 1788 * Retrieves the DIT content rule with the specified name from the server 1789 * schema. 1790 * 1791 * @param ruleName The name of the DIT structure rule to retrieve. It must 1792 * not be {@code null}. 1793 * 1794 * @return The requested DIT structure rule, or {@code null} if there is no 1795 * such rule defined in the server schema. 1796 */ 1797 @Nullable() 1798 public DITStructureRuleDefinition getDITStructureRuleByName( 1799 @NotNull final String ruleName) 1800 { 1801 Validator.ensureNotNull(ruleName); 1802 1803 return dsrMapByName.get(StaticUtils.toLowerCase(ruleName)); 1804 } 1805 1806 1807 1808 /** 1809 * Retrieves the DIT content rule associated with the specified name form from 1810 * the server schema. 1811 * 1812 * @param nameForm The name or OID of the name form for which to retrieve 1813 * the associated DIT structure rule. 1814 * 1815 * @return The requested DIT structure rule, or {@code null} if there is no 1816 * such rule defined in the server schema. 1817 */ 1818 @Nullable() 1819 public DITStructureRuleDefinition getDITStructureRuleByNameForm( 1820 @NotNull final String nameForm) 1821 { 1822 Validator.ensureNotNull(nameForm); 1823 1824 return dsrMapByNameForm.get(StaticUtils.toLowerCase(nameForm)); 1825 } 1826 1827 1828 1829 /** 1830 * Retrieves the set of matching rule definitions contained in the server 1831 * schema. 1832 * 1833 * @return The set of matching rule definitions contained in the server 1834 * schema. 1835 */ 1836 @NotNull() 1837 public Set<MatchingRuleDefinition> getMatchingRules() 1838 { 1839 return mrSet; 1840 } 1841 1842 1843 1844 /** 1845 * Retrieves the matching rule with the specified name or OID from the server 1846 * schema. 1847 * 1848 * @param name The name or OID of the matching rule to retrieve. It must 1849 * not be {@code null}. 1850 * 1851 * @return The requested matching rule, or {@code null} if there is no 1852 * such rule defined in the server schema. 1853 */ 1854 @Nullable() 1855 public MatchingRuleDefinition getMatchingRule(@NotNull final String name) 1856 { 1857 Validator.ensureNotNull(name); 1858 1859 return mrMap.get(StaticUtils.toLowerCase(name)); 1860 } 1861 1862 1863 1864 /** 1865 * Retrieves the set of matching rule use definitions contained in the server 1866 * schema. 1867 * 1868 * @return The set of matching rule use definitions contained in the server 1869 * schema. 1870 */ 1871 @NotNull() 1872 public Set<MatchingRuleUseDefinition> getMatchingRuleUses() 1873 { 1874 return mruSet; 1875 } 1876 1877 1878 1879 /** 1880 * Retrieves the matching rule use with the specified name or OID from the 1881 * server schema. 1882 * 1883 * @param name The name or OID of the matching rule use to retrieve. It 1884 * must not be {@code null}. 1885 * 1886 * @return The requested matching rule, or {@code null} if there is no 1887 * such matching rule use defined in the server schema. 1888 */ 1889 @Nullable() 1890 public MatchingRuleUseDefinition getMatchingRuleUse( 1891 @NotNull final String name) 1892 { 1893 Validator.ensureNotNull(name); 1894 1895 return mruMap.get(StaticUtils.toLowerCase(name)); 1896 } 1897 1898 1899 1900 /** 1901 * Retrieves the set of name form definitions contained in the server schema. 1902 * 1903 * @return The set of name form definitions contained in the server schema. 1904 */ 1905 @NotNull() 1906 public Set<NameFormDefinition> getNameForms() 1907 { 1908 return nfSet; 1909 } 1910 1911 1912 1913 /** 1914 * Retrieves the name form with the specified name or OID from the server 1915 * schema. 1916 * 1917 * @param name The name or OID of the name form to retrieve. It must not be 1918 * {@code null}. 1919 * 1920 * @return The requested name form, or {@code null} if there is no 1921 * such rule defined in the server schema. 1922 */ 1923 @Nullable() 1924 public NameFormDefinition getNameFormByName(@NotNull final String name) 1925 { 1926 Validator.ensureNotNull(name); 1927 1928 return nfMapByName.get(StaticUtils.toLowerCase(name)); 1929 } 1930 1931 1932 1933 /** 1934 * Retrieves the name form associated with the specified structural object 1935 * class from the server schema. 1936 * 1937 * @param objectClass The name or OID of the structural object class for 1938 * which to retrieve the associated name form. It must 1939 * not be {@code null}. 1940 * 1941 * @return The requested name form, or {@code null} if there is no 1942 * such rule defined in the server schema. 1943 */ 1944 @NotNull() 1945 public NameFormDefinition getNameFormByObjectClass( 1946 @NotNull final String objectClass) 1947 { 1948 Validator.ensureNotNull(objectClass); 1949 1950 return nfMapByOC.get(StaticUtils.toLowerCase(objectClass)); 1951 } 1952 1953 1954 1955 /** 1956 * Retrieves the set of object class definitions contained in the server 1957 * schema. 1958 * 1959 * @return The set of object class definitions contained in the server 1960 * schema. 1961 */ 1962 @NotNull() 1963 public Set<ObjectClassDefinition> getObjectClasses() 1964 { 1965 return ocSet; 1966 } 1967 1968 1969 1970 /** 1971 * Retrieves the set of abstract object class definitions contained in the 1972 * server schema. 1973 * 1974 * @return The set of abstract object class definitions contained in the 1975 * server schema. 1976 */ 1977 @NotNull() 1978 public Set<ObjectClassDefinition> getAbstractObjectClasses() 1979 { 1980 return abstractOCSet; 1981 } 1982 1983 1984 1985 /** 1986 * Retrieves the set of auxiliary object class definitions contained in the 1987 * server schema. 1988 * 1989 * @return The set of auxiliary object class definitions contained in the 1990 * server schema. 1991 */ 1992 @NotNull() 1993 public Set<ObjectClassDefinition> getAuxiliaryObjectClasses() 1994 { 1995 return auxiliaryOCSet; 1996 } 1997 1998 1999 2000 /** 2001 * Retrieves the set of structural object class definitions contained in the 2002 * server schema. 2003 * 2004 * @return The set of structural object class definitions contained in the 2005 * server schema. 2006 */ 2007 @NotNull() 2008 public Set<ObjectClassDefinition> getStructuralObjectClasses() 2009 { 2010 return structuralOCSet; 2011 } 2012 2013 2014 2015 /** 2016 * Retrieves the object class with the specified name or OID from the server 2017 * schema. 2018 * 2019 * @param name The name or OID of the object class to retrieve. It must 2020 * not be {@code null}. 2021 * 2022 * @return The requested object class, or {@code null} if there is no such 2023 * class defined in the server schema. 2024 */ 2025 @Nullable() 2026 public ObjectClassDefinition getObjectClass(@NotNull final String name) 2027 { 2028 Validator.ensureNotNull(name); 2029 2030 return ocMap.get(StaticUtils.toLowerCase(name)); 2031 } 2032 2033 2034 2035 /** 2036 * Retrieves a hash code for this schema object. 2037 * 2038 * @return A hash code for this schema object. 2039 */ 2040 @Override() 2041 public int hashCode() 2042 { 2043 int hc; 2044 try 2045 { 2046 hc = schemaEntry.getParsedDN().hashCode(); 2047 } 2048 catch (final Exception e) 2049 { 2050 Debug.debugException(e); 2051 hc = StaticUtils.toLowerCase(schemaEntry.getDN()).hashCode(); 2052 } 2053 2054 Attribute a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_SYNTAX); 2055 if (a != null) 2056 { 2057 hc += a.hashCode(); 2058 } 2059 2060 a = schemaEntry.getAttribute(ATTR_MATCHING_RULE); 2061 if (a != null) 2062 { 2063 hc += a.hashCode(); 2064 } 2065 2066 a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_TYPE); 2067 if (a != null) 2068 { 2069 hc += a.hashCode(); 2070 } 2071 2072 a = schemaEntry.getAttribute(ATTR_OBJECT_CLASS); 2073 if (a != null) 2074 { 2075 hc += a.hashCode(); 2076 } 2077 2078 a = schemaEntry.getAttribute(ATTR_NAME_FORM); 2079 if (a != null) 2080 { 2081 hc += a.hashCode(); 2082 } 2083 2084 a = schemaEntry.getAttribute(ATTR_DIT_CONTENT_RULE); 2085 if (a != null) 2086 { 2087 hc += a.hashCode(); 2088 } 2089 2090 a = schemaEntry.getAttribute(ATTR_DIT_STRUCTURE_RULE); 2091 if (a != null) 2092 { 2093 hc += a.hashCode(); 2094 } 2095 2096 a = schemaEntry.getAttribute(ATTR_MATCHING_RULE_USE); 2097 if (a != null) 2098 { 2099 hc += a.hashCode(); 2100 } 2101 2102 return hc; 2103 } 2104 2105 2106 2107 /** 2108 * Indicates whether the provided object is equal to this schema object. 2109 * 2110 * @param o The object for which to make the determination. 2111 * 2112 * @return {@code true} if the provided object is equal to this schema 2113 * object, or {@code false} if not. 2114 */ 2115 @Override() 2116 public boolean equals(@Nullable final Object o) 2117 { 2118 if (o == null) 2119 { 2120 return false; 2121 } 2122 2123 if (o == this) 2124 { 2125 return true; 2126 } 2127 2128 if (! (o instanceof Schema)) 2129 { 2130 return false; 2131 } 2132 2133 final Schema s = (Schema) o; 2134 2135 try 2136 { 2137 if (! schemaEntry.getParsedDN().equals(s.schemaEntry.getParsedDN())) 2138 { 2139 return false; 2140 } 2141 } 2142 catch (final Exception e) 2143 { 2144 Debug.debugException(e); 2145 if (! schemaEntry.getDN().equalsIgnoreCase(s.schemaEntry.getDN())) 2146 { 2147 return false; 2148 } 2149 } 2150 2151 return (asSet.equals(s.asSet) && 2152 mrSet.equals(s.mrSet) && 2153 atSet.equals(s.atSet) && 2154 ocSet.equals(s.ocSet) && 2155 nfSet.equals(s.nfSet) && 2156 dcrSet.equals(s.dcrSet) && 2157 dsrSet.equals(s.dsrSet) && 2158 mruSet.equals(s.mruSet)); 2159 } 2160 2161 2162 2163 /** 2164 * Retrieves a string representation of the associated schema entry. 2165 * 2166 * @return A string representation of the associated schema entry. 2167 */ 2168 @Override() 2169 @NotNull() 2170 public String toString() 2171 { 2172 return schemaEntry.toString(); 2173 } 2174}