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