001/* 002 * Copyright 2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 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) 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.unboundidds.tools; 037 038 039 040import java.io.OutputStream; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.Iterator; 046import java.util.LinkedHashMap; 047import java.util.List; 048import java.util.Map; 049import java.util.Set; 050import java.util.TreeMap; 051import java.util.TreeSet; 052import java.util.concurrent.atomic.AtomicInteger; 053import java.util.concurrent.atomic.AtomicReference; 054 055import com.unboundid.ldap.sdk.Entry; 056import com.unboundid.ldap.sdk.LDAPConnection; 057import com.unboundid.ldap.sdk.LDAPException; 058import com.unboundid.ldap.sdk.ResultCode; 059import com.unboundid.ldap.sdk.RootDSE; 060import com.unboundid.ldap.sdk.SearchRequest; 061import com.unboundid.ldap.sdk.SearchScope; 062import com.unboundid.ldap.sdk.Version; 063import com.unboundid.ldap.sdk.schema.AttributeSyntaxDefinition; 064import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 065import com.unboundid.ldap.sdk.schema.DITContentRuleDefinition; 066import com.unboundid.ldap.sdk.schema.DITStructureRuleDefinition; 067import com.unboundid.ldap.sdk.schema.MatchingRuleDefinition; 068import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition; 069import com.unboundid.ldap.sdk.schema.NameFormDefinition; 070import com.unboundid.ldap.sdk.schema.ObjectClassDefinition; 071import com.unboundid.ldap.sdk.schema.Schema; 072import com.unboundid.ldap.sdk.unboundidds.controls. 073 ExtendedSchemaInfoRequestControl; 074import com.unboundid.util.Debug; 075import com.unboundid.util.MultiServerLDAPCommandLineTool; 076import com.unboundid.util.NotNull; 077import com.unboundid.util.Nullable; 078import com.unboundid.util.OID; 079import com.unboundid.util.StaticUtils; 080import com.unboundid.util.ThreadSafety; 081import com.unboundid.util.ThreadSafetyLevel; 082import com.unboundid.util.args.ArgumentException; 083import com.unboundid.util.args.ArgumentParser; 084import com.unboundid.util.args.BooleanArgument; 085import com.unboundid.util.args.DNArgument; 086import com.unboundid.util.args.StringArgument; 087 088import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 089 090 091 092/** 093 * This class implements a command-line tool that can be used to retrieve the 094 * schemas from two LDAP servers and identify any differences between them. 095 * <BR> 096 * <BLOCKQUOTE> 097 * <B>NOTE:</B> This class, and other classes within the 098 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 099 * supported for use against Ping Identity, UnboundID, and 100 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 101 * for proprietary functionality or for external specifications that are not 102 * considered stable or mature enough to be guaranteed to work in an 103 * interoperable way with other types of LDAP servers. 104 * </BLOCKQUOTE> 105 * <BR> 106 * Comparisons that this tool may perform include: 107 * <UL> 108 * <LI>Definitions that are present in one server but not another.</LI> 109 * <LI>Corresponding definitions with the same OID but different names or sets 110 * of names.</LI> 111 * <LI>Corresponding definitions with different descriptions, obsolete state, 112 * or sets of extensions.</LI> 113 * <LI>Corresponding attribute types with differences in syntaxes, matching 114 * rules, superior type, single-valued/multivalued behavior, usage, 115 * collective state, or NO-USER-MODIFICATION state.</LI> 116 * <LI>Corresponding object classes with differences in required or optional 117 * attributes, superior class, or object class type.</LI> 118 * <LI>Corresponding DIT content rules with differences in required, optional, 119 * or prohibited attributes, or allowed auxiliary classes.</LI> 120 * <LI>Corresponding name forms with differences in structural class, 121 * required attributes, or optional attributes.</LI> 122 * <LI>Corresponding DIT structure rules with different name form IDs or 123 * superior rule IDs.</LI> 124 * <LI>Corresponding matching rule uses with different sets of applicable 125 * attribute types.</LI> 126 * </UL> 127 */ 128@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 129public final class CompareLDAPSchemas 130 extends MultiServerLDAPCommandLineTool 131{ 132 /** 133 * The column at which long lines should be wrapped. 134 */ 135 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 136 137 138 139 /** 140 * The index number used to reference the first server. 141 */ 142 private static final int FIRST_SERVER_INDEX = 0; 143 144 145 146 /** 147 * The index number used to reference the second server. 148 */ 149 private static final int SECOND_SERVER_INDEX = 1; 150 151 152 153 /** 154 * The name of the command-line argument used to indicate that the tool should 155 * not examine schema elements that have an extension with a given name and 156 * value. 157 */ 158 @NotNull private static final String 159 ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE = 160 "excludeElementsWithExtensionValue"; 161 162 163 164 /** 165 * The name of the command-line argument used to indicate that the tool should 166 * not examine schema elements with names matching a specified prefix. 167 */ 168 @NotNull private static final String 169 ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX = 170 "excludeElementsWithNameMatchingPrefix"; 171 172 173 174 /** 175 * The name of the command-line argument used to specify the DN of the first 176 * server's subschema subentry. 177 */ 178 @NotNull private static final String ARG_NAME_FIRST_SCHEMA_ENTRY_DN = 179 "firstSchemaEntryDN"; 180 181 182 183 /** 184 * The name of the command-line argument used to indicate that the tool should 185 * use the get extended schema info request control if the server reports that 186 * it is supported. 187 */ 188 @NotNull private static final String ARG_NAME_GET_EXTENDED_SCHEMA_INFO = 189 "getExtendedSchemaInfo"; 190 191 192 193 /** 194 * The name of the command-line argument used to indicate that the tool should 195 * ignore differences in element descriptions. 196 */ 197 @NotNull private static final String ARG_NAME_IGNORE_DESCRIPTIONS = 198 "ignoreDescriptions"; 199 200 201 202 /** 203 * The name of the command-line argument used to indicate that the tool should 204 * ignore differences in element extensions. 205 */ 206 @NotNull private static final String ARG_NAME_IGNORE_EXTENSIONS = 207 "ignoreExtensions"; 208 209 210 211 /** 212 * The name of the command-line argument used to indicate that the tool should 213 * only examine schema elements that have an extension with a given name and 214 * value. 215 */ 216 @NotNull private static final String 217 ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE = 218 "includeElementsWithExtensionValue"; 219 220 221 222 /** 223 * The name of the command-line argument used to indicate that the tool should 224 * only examine schema elements with names matching a specified prefix. 225 */ 226 @NotNull private static final String 227 ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX = 228 "includeElementsWithNameMatchingPrefix"; 229 230 231 232 /** 233 * The name of the command-line argument used to specify the types of schema 234 * elements that the server should examine. 235 */ 236 @NotNull private static final String ARG_NAME_SCHEMA_ELEMENT_TYPE = 237 "schemaElementType"; 238 239 240 241 /** 242 * The name of the command-line argument used to specify the DN of the second 243 * server's subschema subentry. 244 */ 245 @NotNull private static final String ARG_NAME_SECOND_SCHEMA_ENTRY_DN = 246 "secondSchemaEntryDN"; 247 248 249 250 /** 251 * The name of the schema element type value that indicates that the tool 252 * should examine attribute syntaxes. 253 */ 254 @NotNull private static final String SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES = 255 "attribute-syntaxes"; 256 257 258 259 /** 260 * The name of the schema element type value that indicates that the tool 261 * should examine attribute types. 262 */ 263 @NotNull private static final String SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES = 264 "attribute-types"; 265 266 267 268 /** 269 * The name of the schema element type value that indicates that the tool 270 * should examine DIT content rules. 271 */ 272 @NotNull private static final String SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES = 273 "dit-content-rules"; 274 275 276 277 /** 278 * The name of the schema element type value that indicates that the tool 279 * should examine DIT structure rules. 280 */ 281 @NotNull private static final String SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES = 282 "dit-structure-rules"; 283 284 285 286 /** 287 * The name of the schema element type value that indicates that the tool 288 * should examine matching rule uses. 289 */ 290 @NotNull private static final String SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES = 291 "matching-rule-uses"; 292 293 294 295 /** 296 * The name of the schema element type value that indicates that the tool 297 * should examine matching rules. 298 */ 299 @NotNull private static final String SCHEMA_ELEMENT_TYPE_MATCHING_RULES = 300 "matching-rules"; 301 302 303 304 /** 305 * The name of the schema element type value that indicates that the tool 306 * should examine object classes. 307 */ 308 @NotNull private static final String SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES = 309 "object-classes"; 310 311 312 313 /** 314 * The name of the schema element type value that indicates that the tool 315 * should examine name forms. 316 */ 317 @NotNull private static final String SCHEMA_ELEMENT_TYPE_NAME_FORMS = 318 "name-forms"; 319 320 321 322 // A reference to the argument parser for this tool. 323 @NotNull private final AtomicReference<ArgumentParser> parserRef; 324 325 // A reference to the completion message for this tool. 326 @NotNull private final AtomicReference<String> completionMessageRef; 327 328 // Indicates whether to ignore differences in schema element descriptions. 329 private boolean ignoreDescriptions; 330 331 // Indicates whether to ignore differences in schema element extensions. 332 private boolean ignoreExtensions; 333 334 // Indicates whether we may include or exclude schema elements based on their 335 // extensions. 336 private boolean includeOrExcludeBasedOnExtensions; 337 338 // Indicates whether we may include or exclude schema elements based on their 339 // name. 340 private boolean includeOrExcludeBasedOnName; 341 342 // A list of name prefixes for schema elements to exclude from the comparison. 343 @NotNull private final List<String> excludeNamePrefixes; 344 345 // A list of name prefixes for schema elements to include in the comparison. 346 @NotNull private final List<String> includeNamePrefixes; 347 348 // A map of schema extension values for schema elements to exclude from the 349 // comparison. 350 @NotNull private final Map<String,List<String>> excludeExtensionValues; 351 352 // A map of schema extension values for schema elements to include in the 353 // comparison. 354 @NotNull private final Map<String,List<String>> includeExtensionValues; 355 356 // The set of schema element types to examine. 357 @NotNull private final Set<String> schemaElementTypes; 358 359 360 361 /** 362 * Runs this tool with the provided set of arguments, using the default 363 * streams for standard output and standard error. 364 * 365 * @param args The command-line arguments to use to run this program. It 366 * must not be {@code null}, but may be empty. 367 */ 368 public static void main(@NotNull final String... args) 369 { 370 final ResultCode resultCode = main(System.out, System.err, args); 371 if (resultCode != ResultCode.SUCCESS) 372 { 373 System.exit(resultCode.intValue()); 374 } 375 } 376 377 378 379 /** 380 * Runs this tool with the provided set of arguments, using the provided 381 * streams for standard output and standard error. 382 * 383 * @param out The output stream to use for standard output. It may be 384 * {@code null} if standard output should be suppressed. 385 * @param err The output stream to use for standard error. It may be 386 * {@code null} if standard error should be suppressed. 387 * @param args The command-line arguments to use to run this program. It 388 * must not be {@code null}, but may be empty. 389 * 390 * @return The result code with information about the result of processing. 391 * A result code of {@code SUCCESS} indicates that all processing 392 * completed successfully and no differences were identified. A 393 * result code of {@code COMPARE_FALSE} indicates that all processing 394 * completed successfully, but one or more differences were 395 * identified between the server schemas. Any other result code 396 * indicates that some problem occurred during processing. 397 */ 398 @NotNull() 399 public static ResultCode main(@Nullable final OutputStream out, 400 @Nullable final OutputStream err, 401 @NotNull final String... args) 402 { 403 final CompareLDAPSchemas tool = new CompareLDAPSchemas(out, err); 404 return tool.runTool(args); 405 } 406 407 408 409 /** 410 * Creates a new instance of this tool with the provided streams for standard 411 * output and standard error. 412 * 413 * @param out The output stream to use for standard output. It may be 414 * {@code null} if standard output should be suppressed. 415 * @param err The output stream to use for standard error. It may be 416 * {@code null} if standard error should be suppressed. 417 */ 418 public CompareLDAPSchemas(@Nullable final OutputStream out, 419 @Nullable final OutputStream err) 420 { 421 super(out, err, new String[] { "first", "second" }, null); 422 423 parserRef = new AtomicReference<>(); 424 completionMessageRef = new AtomicReference<>(); 425 426 schemaElementTypes = new HashSet<>(StaticUtils.setOf( 427 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES, 428 SCHEMA_ELEMENT_TYPE_MATCHING_RULES, 429 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES, 430 SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES, 431 SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES, 432 SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES, 433 SCHEMA_ELEMENT_TYPE_NAME_FORMS, 434 SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)); 435 436 ignoreDescriptions = false; 437 ignoreExtensions = false; 438 includeNamePrefixes = new ArrayList<>(); 439 excludeNamePrefixes = new ArrayList<>(); 440 includeExtensionValues = new HashMap<>(); 441 excludeExtensionValues = new HashMap<>(); 442 } 443 444 445 446 /** 447 * {@inheritDoc} 448 */ 449 @Override() 450 @NotNull() 451 public String getToolName() 452 { 453 return "compare-ldap-schemas"; 454 } 455 456 457 458 /** 459 * {@inheritDoc} 460 */ 461 @Override() 462 @NotNull() 463 public String getToolDescription() 464 { 465 return INFO_COMPARE_SCHEMA_TOOL_DESC.get(); 466 } 467 468 469 470 /** 471 * {@inheritDoc} 472 */ 473 @Override() 474 @NotNull() 475 public String getToolVersion() 476 { 477 return Version.getNumericVersionString(); 478 } 479 480 481 482 /** 483 * {@inheritDoc} 484 */ 485 @Override() 486 protected boolean includeAlternateLongIdentifiers() 487 { 488 return true; 489 } 490 491 492 493 /** 494 * {@inheritDoc} 495 */ 496 @Override() 497 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 498 throws ArgumentException 499 { 500 parserRef.set(parser); 501 502 final DNArgument firstSchemaEntryDNArg = new DNArgument(null, 503 ARG_NAME_FIRST_SCHEMA_ENTRY_DN, false, 1, null, 504 INFO_COMPARE_SCHEMA_ARG_DESC_FIRST_SCHEMA_ENTRY_DN.get()); 505 firstSchemaEntryDNArg.addLongIdentifier("first-schema-entry-dn", true); 506 firstSchemaEntryDNArg.addLongIdentifier("firstSchemaEntry", true); 507 firstSchemaEntryDNArg.addLongIdentifier("first-schema-entry", true); 508 firstSchemaEntryDNArg.addLongIdentifier("firstSchemaDN", true); 509 firstSchemaEntryDNArg.addLongIdentifier("first-schema-dn", true); 510 firstSchemaEntryDNArg.addLongIdentifier("firstSchema", true); 511 firstSchemaEntryDNArg.addLongIdentifier("first-schema", true); 512 parser.addArgument(firstSchemaEntryDNArg); 513 514 final DNArgument secondSchemaEntryDNArg = new DNArgument(null, 515 ARG_NAME_SECOND_SCHEMA_ENTRY_DN, false, 1, null, 516 INFO_COMPARE_SCHEMA_ARG_DESC_SECOND_SCHEMA_ENTRY_DN.get()); 517 secondSchemaEntryDNArg.addLongIdentifier("second-schema-entry-dn", true); 518 secondSchemaEntryDNArg.addLongIdentifier("secondSchemaEntry", true); 519 secondSchemaEntryDNArg.addLongIdentifier("second-schema-entry", true); 520 secondSchemaEntryDNArg.addLongIdentifier("secondSchemaDN", true); 521 secondSchemaEntryDNArg.addLongIdentifier("second-schema-dn", true); 522 secondSchemaEntryDNArg.addLongIdentifier("secondSchema", true); 523 secondSchemaEntryDNArg.addLongIdentifier("second-schema", true); 524 parser.addArgument(secondSchemaEntryDNArg); 525 526 final StringArgument schemaElementTypesArg = new StringArgument(null, 527 ARG_NAME_SCHEMA_ELEMENT_TYPE, false, 0, 528 INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_SCHEMA_ELEMENT_TYPE.get(), 529 INFO_COMPARE_SCHEMA_ARG_DESC_SCHEMA_ELEMENT_TYPE.get( 530 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES, 531 SCHEMA_ELEMENT_TYPE_MATCHING_RULES, 532 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES, 533 SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES, 534 SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES, 535 SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES, 536 SCHEMA_ELEMENT_TYPE_NAME_FORMS, 537 SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)); 538 schemaElementTypesArg.addLongIdentifier("schema-element-types", true); 539 parser.addArgument(schemaElementTypesArg); 540 541 final BooleanArgument getExtendedSchemaInfoArg = new BooleanArgument(null, 542 ARG_NAME_GET_EXTENDED_SCHEMA_INFO, 1, 543 INFO_COMPARE_SCHEMA_ARG_DESC_GET_EXTENDED_SCHEMA_INFO.get()); 544 getExtendedSchemaInfoArg.addLongIdentifier("get-extended-schema-info", 545 true); 546 parser.addArgument(getExtendedSchemaInfoArg); 547 548 final BooleanArgument ignoreDescriptionsArg = new BooleanArgument(null, 549 ARG_NAME_IGNORE_DESCRIPTIONS, 1, 550 INFO_COMPARE_SCHEMA_ARG_DESC_IGNORE_DESCRIPTIONS.get()); 551 ignoreDescriptionsArg.addLongIdentifier("ignore-descriptions", true); 552 ignoreDescriptionsArg.addLongIdentifier("ignoreDescription", true); 553 ignoreDescriptionsArg.addLongIdentifier("ignore-description", true); 554 parser.addArgument(ignoreDescriptionsArg); 555 556 final BooleanArgument ignoreExtensionsArg = new BooleanArgument(null, 557 ARG_NAME_IGNORE_EXTENSIONS, 1, 558 INFO_COMPARE_SCHEMA_ARG_DESC_IGNORE_EXTENSIONS.get()); 559 ignoreExtensionsArg.addLongIdentifier("ignore-extensions", true); 560 ignoreExtensionsArg.addLongIdentifier("ignoreExtension", true); 561 ignoreExtensionsArg.addLongIdentifier("ignore-extension", true); 562 parser.addArgument(ignoreExtensionsArg); 563 564 final StringArgument includeElementsWithNameMatchingPrefixArg = 565 new StringArgument(null, 566 ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, false, 0, 567 INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_PREFIX.get(), 568 INFO_COMPARE_SCHEMA_ARG_DESC_INCLUDE_NAME_MATCHING_PREFIX.get()); 569 includeElementsWithNameMatchingPrefixArg.addLongIdentifier( 570 "include-elements-with-name-matching-prefix", true); 571 parser.addArgument(includeElementsWithNameMatchingPrefixArg); 572 573 final StringArgument excludeElementsWithNameMatchingPrefixArg = 574 new StringArgument(null, 575 ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, false, 0, 576 INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_PREFIX.get(), 577 INFO_COMPARE_SCHEMA_ARG_DESC_EXCLUDE_NAME_MATCHING_PREFIX.get()); 578 excludeElementsWithNameMatchingPrefixArg.addLongIdentifier( 579 "exclude-elements-with-name-matching-prefix", true); 580 parser.addArgument(excludeElementsWithNameMatchingPrefixArg); 581 582 final StringArgument includeElementsWithExtensionValueArg = 583 new StringArgument(null, 584 ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, false, 0, 585 INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_EXTENSION_VALUE.get(), 586 INFO_COMPARE_SCHEMA_ARG_DESC_INCLUDE_EXTENSION_VALUE.get()); 587 includeElementsWithExtensionValueArg.addLongIdentifier( 588 "include-elements-with-extension-value", true); 589 parser.addArgument(includeElementsWithExtensionValueArg); 590 591 final StringArgument excludeElementsWithExtensionValueArg = 592 new StringArgument(null, 593 ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, false, 0, 594 INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_EXTENSION_VALUE.get(), 595 INFO_COMPARE_SCHEMA_ARG_DESC_EXCLUDE_EXTENSION_VALUE.get()); 596 excludeElementsWithExtensionValueArg.addLongIdentifier( 597 "exclude-elements-with-extension-value", true); 598 parser.addArgument(excludeElementsWithExtensionValueArg); 599 } 600 601 602 603 /** 604 * {@inheritDoc} 605 */ 606 @Override() 607 public void doExtendedNonLDAPArgumentValidation() 608 throws ArgumentException 609 { 610 // Identify the types of schema elements to examine. 611 final ArgumentParser parser = parserRef.get(); 612 final StringArgument schemaElementTypesArg = 613 parser.getStringArgument(ARG_NAME_SCHEMA_ELEMENT_TYPE); 614 if ((schemaElementTypesArg != null) && schemaElementTypesArg.isPresent()) 615 { 616 schemaElementTypes.clear(); 617 for (final String value : schemaElementTypesArg.getValues()) 618 { 619 if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES)) 620 { 621 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES); 622 } 623 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_MATCHING_RULES)) 624 { 625 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_MATCHING_RULES); 626 } 627 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES)) 628 { 629 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES); 630 } 631 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES)) 632 { 633 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES); 634 } 635 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES)) 636 { 637 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES); 638 } 639 else if (value.equalsIgnoreCase( 640 SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES)) 641 { 642 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES); 643 } 644 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_NAME_FORMS)) 645 { 646 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_NAME_FORMS); 647 } 648 else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)) 649 { 650 schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES); 651 } 652 else 653 { 654 throw new ArgumentException( 655 ERR_COMPARE_SCHEMA_INVALID_SCHEMA_ELEMENT_TYPE.get(value, 656 ARG_NAME_SCHEMA_ELEMENT_TYPE, 657 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES, 658 SCHEMA_ELEMENT_TYPE_MATCHING_RULES, 659 SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES, 660 SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES, 661 SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES, 662 SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES, 663 SCHEMA_ELEMENT_TYPE_NAME_FORMS, 664 SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)); 665 } 666 } 667 } 668 669 670 // Determine whether to ignore schema element descriptions or extensions. 671 final BooleanArgument ignoreDescriptionsArg = 672 parser.getBooleanArgument(ARG_NAME_IGNORE_DESCRIPTIONS); 673 ignoreDescriptions = 674 ((ignoreDescriptionsArg != null) && ignoreDescriptionsArg.isPresent()); 675 676 final BooleanArgument ignoreExtensionsArg = 677 parser.getBooleanArgument(ARG_NAME_IGNORE_EXTENSIONS); 678 ignoreExtensions = 679 ((ignoreExtensionsArg != null) && ignoreExtensionsArg.isPresent()); 680 681 682 // Identify the schema element name prefixes to include and exclude. 683 getNamePrefixes(ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, 684 includeNamePrefixes); 685 getNamePrefixes(ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, 686 excludeNamePrefixes); 687 includeOrExcludeBasedOnName = (! includeNamePrefixes.isEmpty()) || 688 (! excludeNamePrefixes.isEmpty()); 689 690 691 // Identify the schema element extension values to include and exclude. 692 getExtensionValues(ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, 693 includeExtensionValues); 694 getExtensionValues(ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, 695 excludeExtensionValues); 696 includeOrExcludeBasedOnExtensions = (! includeExtensionValues.isEmpty()) || 697 (! excludeExtensionValues.isEmpty()); 698 } 699 700 701 702 /** 703 * Populates the provided list with the set of schema element prefixes 704 * contained in the specified argument. 705 * 706 * @param argumentName The name of the argument whose values will be used to 707 * populate the given list. 708 * @param prefixList The list to be updated to include the values of the 709 * specified argument. 710 */ 711 private void getNamePrefixes(@NotNull final String argumentName, 712 @NotNull final List<String> prefixList) 713 { 714 prefixList.clear(); 715 final StringArgument arg = parserRef.get().getStringArgument(argumentName); 716 if ((arg == null) || (! arg.isPresent())) 717 { 718 return; 719 } 720 721 for (final String value : arg.getValues()) 722 { 723 prefixList.add(StaticUtils.toLowerCase(value)); 724 } 725 } 726 727 728 729 /** 730 * Populates the provided map with the set of schema element extension 731 * name-value pairs contained in the specified argument. 732 * 733 * @param argumentName The name of the argument whose values will be used to 734 * populate the given map. 735 * @param extensionMap The map to be updated to include the values of the 736 * specified argument. 737 * 738 * @throws ArgumentException If there is a problem with any of the values of 739 * the specified argument. 740 */ 741 private void getExtensionValues( 742 @NotNull final String argumentName, 743 @NotNull final Map<String,List<String>> extensionMap) 744 throws ArgumentException 745 { 746 extensionMap.clear(); 747 final StringArgument arg = parserRef.get().getStringArgument(argumentName); 748 if ((arg == null) || (! arg.isPresent())) 749 { 750 return; 751 } 752 753 for (final String value : arg.getValues()) 754 { 755 final int equalPos = value.indexOf('='); 756 if (equalPos < 0) 757 { 758 throw new ArgumentException( 759 ERR_COMPARE_SCHEMA_EXTENSION_VALUE_NO_EQUALS.get(argumentName, 760 value)); 761 } 762 763 final String extensionName = 764 StaticUtils.toLowerCase(value.substring(0, equalPos)); 765 if (extensionName.isEmpty()) 766 { 767 throw new ArgumentException( 768 ERR_COMPARE_SCHEMA_EXTENSION_VALUE_EMPTY_NAME.get(argumentName, 769 value)); 770 } 771 772 final String extensionValue = 773 StaticUtils.toLowerCase(value.substring(equalPos + 1)); 774 if (extensionValue.isEmpty()) 775 { 776 throw new ArgumentException( 777 ERR_COMPARE_SCHEMA_EXTENSION_VALUE_EMPTY_VALUE.get(argumentName, 778 value)); 779 } 780 781 List<String> valueList = extensionMap.get(extensionName); 782 if (valueList == null) 783 { 784 valueList = new ArrayList<>(); 785 extensionMap.put(extensionName, valueList); 786 } 787 788 valueList.add(extensionValue); 789 } 790 } 791 792 793 794 /** 795 * {@inheritDoc} 796 */ 797 @Override() 798 public boolean supportsInteractiveMode() 799 { 800 return true; 801 } 802 803 804 805 /** 806 * {@inheritDoc} 807 */ 808 @Override() 809 public boolean defaultsToInteractiveMode() 810 { 811 return true; 812 } 813 814 815 816 /** 817 * {@inheritDoc} 818 */ 819 @Override() 820 public boolean supportsPropertiesFile() 821 { 822 return true; 823 } 824 825 826 827 /** 828 * {@inheritDoc} 829 */ 830 @Override() 831 protected boolean supportsOutputFile() 832 { 833 return true; 834 } 835 836 837 838 /** 839 * {@inheritDoc} 840 */ 841 @Override() 842 protected boolean supportsDebugLogging() 843 { 844 return true; 845 } 846 847 848 849 /** 850 * {@inheritDoc} 851 */ 852 @Override() 853 protected boolean logToolInvocationByDefault() 854 { 855 return false; 856 } 857 858 859 860 /** 861 * {@inheritDoc} 862 */ 863 @Override() 864 @Nullable() 865 protected String getToolCompletionMessage() 866 { 867 return completionMessageRef.get(); 868 } 869 870 871 872 /** 873 * {@inheritDoc} 874 */ 875 @Override() 876 @NotNull() 877 public ResultCode doToolProcessing() 878 { 879 // Get the schemas from each of the servers. 880 final Schema firstServerSchema; 881 final Map<String,LDAPException> firstUnparsableAttributeSyntaxes = 882 new LinkedHashMap<>(); 883 final Map<String,LDAPException> firstUnparsableMatchingRules = 884 new LinkedHashMap<>(); 885 final Map<String,LDAPException> firstUnparsableAttributeTypes = 886 new LinkedHashMap<>(); 887 final Map<String,LDAPException> firstUnparsableObjectClasses = 888 new LinkedHashMap<>(); 889 final Map<String,LDAPException> firstUnparsableDITContentRules = 890 new LinkedHashMap<>(); 891 final Map<String,LDAPException> firstUnparsableDITStructureRules = 892 new LinkedHashMap<>(); 893 final Map<String,LDAPException> firstUnparsableNameForms = 894 new LinkedHashMap<>(); 895 final Map<String,LDAPException> firstUnparsableMatchingRuleUses = 896 new LinkedHashMap<>(); 897 try 898 { 899 firstServerSchema = getSchema(FIRST_SERVER_INDEX, 900 ARG_NAME_FIRST_SCHEMA_ENTRY_DN, 901 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 902 firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules, 903 firstUnparsableAttributeTypes, firstUnparsableObjectClasses, 904 firstUnparsableDITContentRules, firstUnparsableDITStructureRules, 905 firstUnparsableNameForms, firstUnparsableMatchingRuleUses); 906 } 907 catch (final LDAPException e) 908 { 909 logCompletionError(e.getMessage()); 910 return e.getResultCode(); 911 } 912 913 final Schema secondServerSchema; 914 final Map<String,LDAPException> secondUnparsableAttributeSyntaxes = 915 new LinkedHashMap<>(); 916 final Map<String,LDAPException> secondUnparsableMatchingRules = 917 new LinkedHashMap<>(); 918 final Map<String,LDAPException> secondUnparsableAttributeTypes = 919 new LinkedHashMap<>(); 920 final Map<String,LDAPException> secondUnparsableObjectClasses = 921 new LinkedHashMap<>(); 922 final Map<String,LDAPException> secondUnparsableDITContentRules = 923 new LinkedHashMap<>(); 924 final Map<String,LDAPException> secondUnparsableDITStructureRules = 925 new LinkedHashMap<>(); 926 final Map<String,LDAPException> secondUnparsableNameForms = 927 new LinkedHashMap<>(); 928 final Map<String,LDAPException> secondUnparsableMatchingRuleUses = 929 new LinkedHashMap<>(); 930 try 931 { 932 secondServerSchema = getSchema(SECOND_SERVER_INDEX, 933 ARG_NAME_SECOND_SCHEMA_ENTRY_DN, 934 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 935 secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules, 936 secondUnparsableAttributeTypes, secondUnparsableObjectClasses, 937 secondUnparsableDITContentRules, secondUnparsableDITStructureRules, 938 secondUnparsableNameForms, secondUnparsableMatchingRuleUses); 939 } 940 catch (final LDAPException e) 941 { 942 logCompletionError(e.getMessage()); 943 return e.getResultCode(); 944 } 945 946 947 // Report on any unparsable schema elements. 948 final AtomicReference<ResultCode> resultCodeRef = new AtomicReference<>(); 949 boolean unparsableElementsEncountered = reportUnparsableSchemaElements( 950 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 951 firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules, 952 firstUnparsableAttributeTypes, firstUnparsableObjectClasses, 953 firstUnparsableDITContentRules, firstUnparsableDITStructureRules, 954 firstUnparsableNameForms, firstUnparsableMatchingRuleUses); 955 956 unparsableElementsEncountered |= reportUnparsableSchemaElements( 957 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 958 secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules, 959 secondUnparsableAttributeTypes, secondUnparsableObjectClasses, 960 secondUnparsableDITContentRules, secondUnparsableDITStructureRules, 961 secondUnparsableNameForms, secondUnparsableMatchingRuleUses); 962 963 if (unparsableElementsEncountered) 964 { 965 resultCodeRef.set(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 966 } 967 968 969 // Validate the different types of schema elements. 970 final AtomicInteger numDifferences = new AtomicInteger(); 971 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES)) 972 { 973 compareAttributeSyntaxes(firstServerSchema, secondServerSchema, 974 numDifferences); 975 } 976 977 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES)) 978 { 979 compareMatchingRules(firstServerSchema, secondServerSchema, 980 numDifferences); 981 } 982 983 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES)) 984 { 985 compareAttributeTypes(firstServerSchema, secondServerSchema, 986 numDifferences); 987 } 988 989 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES)) 990 { 991 compareObjectClasses(firstServerSchema, secondServerSchema, 992 numDifferences); 993 } 994 995 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES)) 996 { 997 compareDITContentRules(firstServerSchema, secondServerSchema, 998 numDifferences); 999 } 1000 1001 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES)) 1002 { 1003 compareDITStructureRules(firstServerSchema, secondServerSchema, 1004 numDifferences); 1005 } 1006 1007 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS)) 1008 { 1009 compareNameForms(firstServerSchema, secondServerSchema, numDifferences); 1010 } 1011 1012 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)) 1013 { 1014 compareMatchingRuleUses(firstServerSchema, secondServerSchema, 1015 numDifferences); 1016 } 1017 1018 1019 // If any errors were encountered, then return an error result code. 1020 // Otherwise, if any differences were encountered, then return a 1021 // COMPARE_FALSE result code. Otherwise, return a SUCCESS result code. 1022 final int differenceCount = numDifferences.get(); 1023 if (unparsableElementsEncountered) 1024 { 1025 switch (differenceCount) 1026 { 1027 case 0: 1028 logCompletionError( 1029 ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_NO_DIFFERENCES.get()); 1030 break; 1031 case 1: 1032 logCompletionError( 1033 ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCE.get()); 1034 break; 1035 default: 1036 logCompletionError( 1037 ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCES.get( 1038 differenceCount)); 1039 break; 1040 } 1041 } 1042 else if (differenceCount > 0) 1043 { 1044 resultCodeRef.compareAndSet(null, ResultCode.COMPARE_FALSE); 1045 if (differenceCount == 1) 1046 { 1047 logCompletionError( 1048 ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCE.get()); 1049 } 1050 else 1051 { 1052 logCompletionError( 1053 ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCES.get(differenceCount)); 1054 } 1055 } 1056 else 1057 { 1058 resultCodeRef.compareAndSet(null, ResultCode.SUCCESS); 1059 final String message = INFO_COMPARE_SCHEMA_SUMMARY_NO_DIFFERENCES.get(); 1060 completionMessageRef.compareAndSet(null, message); 1061 wrapOut(0, WRAP_COLUMN, message); 1062 } 1063 1064 return resultCodeRef.get(); 1065 } 1066 1067 1068 1069 /** 1070 * Retrieves the schema from the specified server. 1071 * 1072 * @param serverIndex 1073 * The index for the server from which to retrieve the schema. 1074 * @param schemaDNArgName 1075 * The name of the argument to use to retrieve the DN of the 1076 * subschema subentry, if specified. It must not be 1077 * {@code null}. 1078 * @param serverLabel 1079 * The label to use to refer to the server. It must not be 1080 * {@code null}. 1081 * @param unparsableAttributeSyntaxes 1082 * A map that will be updated with information about any 1083 * unparsable attribute syntax definitions found in the schema 1084 * from the specified server. Each key will be the unparsable 1085 * definition, and the corresponding value will be the exception 1086 * caught while trying to parse it. It must not be {@code null}. 1087 * @param unparsableMatchingRules 1088 * A map that will be updated with information about any 1089 * unparsable matching rule definitions found in the schema 1090 * from the specified server. Each key will be the unparsable 1091 * definition, and the corresponding value will be the exception 1092 * caught while trying to parse it. It must not be {@code null}. 1093 * @param unparsableAttributeTypes 1094 * A map that will be updated with information about any 1095 * unparsable attribute type definitions found in the schema 1096 * from the specified server. Each key will be the unparsable 1097 * definition, and the corresponding value will be the exception 1098 * caught while trying to parse it. It must not be {@code null}. 1099 * @param unparsableObjectClasses 1100 * A map that will be updated with information about any 1101 * unparsable object class definitions found in the schema 1102 * from the specified server. Each key will be the unparsable 1103 * definition, and the corresponding value will be the exception 1104 * caught while trying to parse it. It must not be {@code null}. 1105 * @param unparsableDITContentRules 1106 * A map that will be updated with information about any 1107 * unparsable DIT content rule definitions found in the schema 1108 * from the specified server. Each key will be the unparsable 1109 * definition, and the corresponding value will be the exception 1110 * caught while trying to parse it. It must not be {@code null}. 1111 * @param unparsableDITStructureRules 1112 * A map that will be updated with information about any 1113 * unparsable DIT structure rule definitions found in the schema 1114 * from the specified server. Each key will be the unparsable 1115 * definition, and the corresponding value will be the exception 1116 * caught while trying to parse it. It must not be {@code null}. 1117 * @param unparsableNameForms 1118 * A map that will be updated with information about any 1119 * unparsable name form definitions found in the schema 1120 * from the specified server. Each key will be the unparsable 1121 * definition, and the corresponding value will be the exception 1122 * caught while trying to parse it. It must not be {@code null}. 1123 * @param unparsableMatchingRuleUses 1124 * A map that will be updated with information about any 1125 * unparsable matching rule use definitions found in the schema 1126 * from the specified server. Each key will be the unparsable 1127 * definition, and the corresponding value will be the exception 1128 * caught while trying to parse it. It must not be {@code null}. 1129 * 1130 * @return The schema retrieved from the server. 1131 * 1132 * @throws LDAPException If a problem occurs while attempting to obtain the 1133 * schema. 1134 */ 1135 @NotNull() 1136 private Schema getSchema(final int serverIndex, 1137 @NotNull final String schemaDNArgName, 1138 @NotNull final String serverLabel, 1139 @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes, 1140 @NotNull final Map<String,LDAPException> unparsableMatchingRules, 1141 @NotNull final Map<String,LDAPException> unparsableAttributeTypes, 1142 @NotNull final Map<String,LDAPException> unparsableObjectClasses, 1143 @NotNull final Map<String,LDAPException> unparsableDITContentRules, 1144 @NotNull final Map<String,LDAPException> unparsableDITStructureRules, 1145 @NotNull final Map<String,LDAPException> unparsableNameForms, 1146 @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses) 1147 throws LDAPException 1148 { 1149 // Establish a connection to the server. 1150 final LDAPConnection conn; 1151 try 1152 { 1153 conn = getConnection(serverIndex); 1154 } 1155 catch (final LDAPException e) 1156 { 1157 Debug.debugException(e); 1158 throw new LDAPException(e.getResultCode(), 1159 ERR_COMPARE_SCHEMA_CANNOT_CONNECT.get(serverLabel, e.getMessage()), 1160 e); 1161 } 1162 1163 final ArgumentParser parser = parserRef.get(); 1164 final BooleanArgument getExtendedSchemaInfoArg = 1165 parser.getBooleanArgument(ARG_NAME_GET_EXTENDED_SCHEMA_INFO); 1166 final boolean getExtendedSchemaInfo = 1167 ((getExtendedSchemaInfoArg != null) && 1168 getExtendedSchemaInfoArg.isPresent()); 1169 1170 1171 try 1172 { 1173 // See if the schema entry DN was specified as an argument. If so, then 1174 // retrieve that entry and parse it as a schema entry. Otherwise, use the 1175 // default method for obtaining the schema. 1176 final String schemaEntryDN; 1177 final DNArgument schemaEntryDNArg = parser.getDNArgument(schemaDNArgName); 1178 if (schemaEntryDNArg.isPresent()) 1179 { 1180 schemaEntryDN = schemaEntryDNArg.getStringValue(); 1181 } 1182 else 1183 { 1184 final RootDSE rootDSE = conn.getRootDSE(); 1185 if (rootDSE == null) 1186 { 1187 throw new LDAPException(ResultCode.LOCAL_ERROR, 1188 ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE.get(serverLabel)); 1189 } 1190 1191 schemaEntryDN = rootDSE.getSubschemaSubentryDN(); 1192 if (schemaEntryDN == null) 1193 { 1194 throw new LDAPException(ResultCode.LOCAL_ERROR, 1195 ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE_SCHEMA_DN.get(serverLabel, 1196 RootDSE.ATTR_SUBSCHEMA_SUBENTRY)); 1197 } 1198 } 1199 1200 final SearchRequest searchRequest = new SearchRequest(schemaEntryDN, 1201 SearchScope.BASE, Schema.SUBSCHEMA_SUBENTRY_FILTER, 1202 Schema.SCHEMA_REQUEST_ATTRS); 1203 if (getExtendedSchemaInfo) 1204 { 1205 searchRequest.addControl(new ExtendedSchemaInfoRequestControl(false)); 1206 } 1207 1208 final Entry schemaEntry = conn.searchForEntry(searchRequest); 1209 if (schemaEntry == null) 1210 { 1211 throw new LDAPException(ResultCode.NO_SUCH_OBJECT, 1212 ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA_ENTRY.get( 1213 String.valueOf(schemaEntryDN), serverLabel)); 1214 } 1215 1216 return new Schema(schemaEntry, unparsableAttributeSyntaxes, 1217 unparsableMatchingRules, unparsableAttributeTypes, 1218 unparsableObjectClasses, unparsableDITContentRules, 1219 unparsableDITStructureRules, unparsableNameForms, 1220 unparsableMatchingRuleUses); 1221 } 1222 catch (final LDAPException e) 1223 { 1224 Debug.debugException(e); 1225 throw new LDAPException(e.getResultCode(), 1226 ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA.get(serverLabel, 1227 e.getMessage()), 1228 e); 1229 } 1230 finally 1231 { 1232 conn.close(); 1233 } 1234 } 1235 1236 1237 1238 /** 1239 * Reports error messages about any unparsable elements found in a server's 1240 * schema. 1241 * 1242 * @param serverLabel 1243 * The label for the associated directory server instance. 1244 * @param unparsableAttributeSyntaxes 1245 * A map with information about any unparsable attribute syntax 1246 * definitions found in the schema. 1247 * @param unparsableMatchingRules 1248 * A map with information about any unparsable matching rule 1249 * definitions found in the schema. 1250 * @param unparsableAttributeTypes 1251 * A map with information about any unparsable attribute type 1252 * definitions found in the schema. 1253 * @param unparsableObjectClasses 1254 * A map with information about any unparsable object class 1255 * definitions found in the schema. 1256 * @param unparsableDITContentRules 1257 * A map with information about any unparsable DIT content rule 1258 * definitions found in the schema. 1259 * @param unparsableDITStructureRules 1260 * A map with information about any unparsable DIT structure rule 1261 * definitions found in the schema. 1262 * @param unparsableNameForms 1263 * A map with information about any unparsable name form 1264 * definitions found in the schema. 1265 * @param unparsableMatchingRuleUses 1266 * A map with information about any unparsable matching rule use 1267 * definitions found in the schema. 1268 * 1269 * @return {@code true} if the schema contained any unparsable elements, or 1270 * {@code false} if not. 1271 */ 1272 private boolean reportUnparsableSchemaElements( 1273 @NotNull final String serverLabel, 1274 @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes, 1275 @NotNull final Map<String,LDAPException> unparsableMatchingRules, 1276 @NotNull final Map<String,LDAPException> unparsableAttributeTypes, 1277 @NotNull final Map<String,LDAPException> unparsableObjectClasses, 1278 @NotNull final Map<String,LDAPException> unparsableDITContentRules, 1279 @NotNull final Map<String,LDAPException> unparsableDITStructureRules, 1280 @NotNull final Map<String,LDAPException> unparsableNameForms, 1281 @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses) 1282 { 1283 boolean unparsableFound = false; 1284 1285 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES)) 1286 { 1287 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1288 unparsableAttributeSyntaxes, 1289 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get()); 1290 } 1291 1292 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES)) 1293 { 1294 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1295 unparsableMatchingRules, 1296 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get()); 1297 } 1298 1299 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES)) 1300 { 1301 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1302 unparsableAttributeTypes, 1303 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get()); 1304 } 1305 1306 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES)) 1307 { 1308 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1309 unparsableObjectClasses, 1310 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get()); 1311 } 1312 1313 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES)) 1314 { 1315 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1316 unparsableDITContentRules, 1317 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get()); 1318 } 1319 1320 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES)) 1321 { 1322 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1323 unparsableDITStructureRules, 1324 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get()); 1325 } 1326 1327 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS)) 1328 { 1329 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1330 unparsableNameForms, 1331 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get()); 1332 } 1333 1334 if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES)) 1335 { 1336 unparsableFound |= reportUnparsableSchemaElements(serverLabel, 1337 unparsableMatchingRuleUses, 1338 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get()); 1339 } 1340 1341 return unparsableFound; 1342 } 1343 1344 1345 1346 /** 1347 * Reports error messages about any unparsable elements of the specified type 1348 * found in a server's schema. 1349 * 1350 * @param serverLabel The label for the associated directory server 1351 * instance. It must not be {@code null}. 1352 * @param unparsableElements The set of unparsable elements of a given type. 1353 * It must not be {@code null}, but may be empty. 1354 * @param elementTypeName The name of the schema element type. It must 1355 * not be {@code null}. 1356 * 1357 * @return {@code true} if the provided map contained information about one 1358 * or more unparsable elements, or {@code false} if not. 1359 */ 1360 private boolean reportUnparsableSchemaElements( 1361 @NotNull final String serverLabel, 1362 @NotNull final Map<String,LDAPException> unparsableElements, 1363 @NotNull final String elementTypeName) 1364 { 1365 for (final Map.Entry<String,LDAPException> e : 1366 unparsableElements.entrySet()) 1367 { 1368 wrapErr(0, WRAP_COLUMN, 1369 ERR_COMPARE_SCHEMA_UNPARSABLE_ELEMENT.get(elementTypeName, 1370 serverLabel, e.getValue().getMessage())); 1371 err(e.getKey()); 1372 err(); 1373 } 1374 1375 return (! unparsableElements.isEmpty()); 1376 } 1377 1378 1379 1380 /** 1381 * Compares the attribute syntax definitions contained in the provided 1382 * schemas. 1383 * 1384 * @param firstServerSchema The schema retrieved from the first server. It 1385 * must not be {@code null}. 1386 * @param secondServerSchema The schema retrieved from the second server. 1387 * It must not be {@code null}. 1388 * @param numDifferences A counter used to keep track of the number of 1389 * differences found between the schemas. It must 1390 * not be {@code null}. 1391 */ 1392 private void compareAttributeSyntaxes( 1393 @NotNull final Schema firstServerSchema, 1394 @NotNull final Schema secondServerSchema, 1395 @NotNull final AtomicInteger numDifferences) 1396 { 1397 // Get the attribute syntax definitions from each of the schemas. 1398 final Map<OID,AttributeSyntaxDefinition> syntaxes1 = 1399 getAttributeSyntaxMap(firstServerSchema); 1400 final Map<OID,AttributeSyntaxDefinition> syntaxes2 = 1401 getAttributeSyntaxMap(secondServerSchema); 1402 1403 1404 // Identify syntaxes that exist in one server but not another. If any are 1405 // found, then report them and remove them from the set. 1406 Iterator<Map.Entry<OID,AttributeSyntaxDefinition>> iterator = 1407 syntaxes1.entrySet().iterator(); 1408 while (iterator.hasNext()) 1409 { 1410 final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next(); 1411 final OID oid = e.getKey(); 1412 if (! syntaxes2.containsKey(oid)) 1413 { 1414 reportDifference( 1415 WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get( 1416 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 1417 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 1418 numDifferences, 1419 e.getValue().toString()); 1420 iterator.remove(); 1421 } 1422 } 1423 1424 iterator = syntaxes2.entrySet().iterator(); 1425 while (iterator.hasNext()) 1426 { 1427 final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next(); 1428 final OID oid = e.getKey(); 1429 if (! syntaxes1.containsKey(oid)) 1430 { 1431 reportDifference( 1432 WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get( 1433 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 1434 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 1435 numDifferences, 1436 e.getValue().toString()); 1437 iterator.remove(); 1438 } 1439 } 1440 1441 1442 // Any remaining syntaxes should exist in both servers. Compare them and 1443 // see if there are any differences between them. 1444 for (final OID oid : syntaxes1.keySet()) 1445 { 1446 final AttributeSyntaxDefinition d1 = syntaxes1.get(oid); 1447 final AttributeSyntaxDefinition d2 = syntaxes2.get(oid); 1448 1449 if (! ignoreDescriptions) 1450 { 1451 compareStringValues( 1452 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(), 1453 oid.toString(), 1454 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 1455 d1.getDescription(), d2.getDescription(), numDifferences); 1456 } 1457 1458 compareExtensions( 1459 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(), 1460 oid.toString(), d1.getExtensions(), d2.getExtensions(), 1461 numDifferences); 1462 } 1463 } 1464 1465 1466 1467 /** 1468 * Retrieves a map of the attribute syntax definitions contained in the 1469 * provided schema, indexed by OID. 1470 * 1471 * @param schema The schema from which to retrieve the attribute syntaxes. 1472 * It must not be {@code null}. 1473 * 1474 * @return A map of the attribute syntax definitions contained in the 1475 * provided schema. 1476 */ 1477 @NotNull() 1478 private Map<OID,AttributeSyntaxDefinition> getAttributeSyntaxMap( 1479 @NotNull final Schema schema) 1480 { 1481 final Map<OID,AttributeSyntaxDefinition> syntaxes = new TreeMap<>(); 1482 for (final AttributeSyntaxDefinition d : schema.getAttributeSyntaxes()) 1483 { 1484 if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS, 1485 d.getExtensions())) 1486 { 1487 syntaxes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 1488 } 1489 } 1490 1491 return syntaxes; 1492 } 1493 1494 1495 1496 /** 1497 * Compares the matching rule definitions contained in the provided schemas. 1498 * 1499 * @param firstServerSchema The schema retrieved from the first server. It 1500 * must not be {@code null}. 1501 * @param secondServerSchema The schema retrieved from the second server. 1502 * It must not be {@code null}. 1503 * @param numDifferences A counter used to keep track of the number of 1504 * differences found between the schemas. It must 1505 * not be {@code null}. 1506 */ 1507 private void compareMatchingRules( 1508 @NotNull final Schema firstServerSchema, 1509 @NotNull final Schema secondServerSchema, 1510 @NotNull final AtomicInteger numDifferences) 1511 { 1512 // Get the matching rule definitions from each of the schemas. 1513 final Map<OID,MatchingRuleDefinition> matchingRules1 = 1514 getMatchingRuleMap(firstServerSchema); 1515 final Map<OID,MatchingRuleDefinition> matchingRules2 = 1516 getMatchingRuleMap(secondServerSchema); 1517 1518 1519 // Identify matching rules that exist in one server but not another. If any 1520 // are found, then report them and remove them from the set. 1521 Iterator<Map.Entry<OID,MatchingRuleDefinition>> iterator = 1522 matchingRules1.entrySet().iterator(); 1523 while (iterator.hasNext()) 1524 { 1525 final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next(); 1526 final OID oid = e.getKey(); 1527 if (! matchingRules2.containsKey(oid)) 1528 { 1529 reportDifference( 1530 WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get( 1531 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 1532 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 1533 numDifferences, 1534 e.getValue().toString()); 1535 iterator.remove(); 1536 } 1537 } 1538 1539 iterator = matchingRules2.entrySet().iterator(); 1540 while (iterator.hasNext()) 1541 { 1542 final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next(); 1543 final OID oid = e.getKey(); 1544 if (! matchingRules1.containsKey(oid)) 1545 { 1546 reportDifference( 1547 WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get( 1548 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 1549 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 1550 numDifferences, 1551 e.getValue().toString()); 1552 iterator.remove(); 1553 } 1554 } 1555 1556 1557 // Any remaining matching rules should exist in both servers. Compare them 1558 // and see if there are any differences between them. 1559 for (final OID oid : matchingRules1.keySet()) 1560 { 1561 final MatchingRuleDefinition d1 = matchingRules1.get(oid); 1562 final MatchingRuleDefinition d2 = matchingRules2.get(oid); 1563 1564 final String identifier = compareNames( 1565 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), 1566 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 1567 1568 compareStringValues( 1569 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier, 1570 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(), 1571 d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences); 1572 1573 compareBooleanValues( 1574 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier, 1575 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 1576 d1.isObsolete(), d2.isObsolete(), numDifferences); 1577 1578 if (! ignoreDescriptions) 1579 { 1580 compareStringValues( 1581 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier, 1582 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 1583 d1.getDescription(), d2.getDescription(), numDifferences); 1584 } 1585 1586 compareExtensions( 1587 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(), 1588 identifier, d1.getExtensions(), d2.getExtensions(), 1589 numDifferences); 1590 } 1591 } 1592 1593 1594 1595 /** 1596 * Retrieves a map of the matching rule definitions contained in the provided 1597 * schema, indexed by OID. 1598 * 1599 * @param schema The schema from which to retrieve the matching rules. It 1600 * must not be {@code null}. 1601 * 1602 * @return A map of the matching rule definitions contained in the provided 1603 * schema. 1604 */ 1605 @NotNull() 1606 private Map<OID,MatchingRuleDefinition> getMatchingRuleMap( 1607 @NotNull final Schema schema) 1608 { 1609 final Map<OID,MatchingRuleDefinition> matchingRules = new TreeMap<>(); 1610 for (final MatchingRuleDefinition d : schema.getMatchingRules()) 1611 { 1612 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 1613 { 1614 matchingRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 1615 } 1616 } 1617 1618 return matchingRules; 1619 } 1620 1621 1622 1623 /** 1624 * Compares the attribute type definitions contained in the provided schemas. 1625 * 1626 * @param firstServerSchema The schema retrieved from the first server. It 1627 * must not be {@code null}. 1628 * @param secondServerSchema The schema retrieved from the second server. 1629 * It must not be {@code null}. 1630 * @param numDifferences A counter used to keep track of the number of 1631 * differences found between the schemas. It must 1632 * not be {@code null}. 1633 */ 1634 private void compareAttributeTypes( 1635 @NotNull final Schema firstServerSchema, 1636 @NotNull final Schema secondServerSchema, 1637 @NotNull final AtomicInteger numDifferences) 1638 { 1639 // Get the attribute type definitions from each of the schemas. 1640 final Map<OID,AttributeTypeDefinition> attributeTypes1 = 1641 getAttributeTypeMap(firstServerSchema); 1642 final Map<OID,AttributeTypeDefinition> attributeTypes2 = 1643 getAttributeTypeMap(secondServerSchema); 1644 1645 1646 // Identify attribute types that exist in one server but not another. If 1647 // any are found, then report them and remove them from the set. 1648 Iterator<Map.Entry<OID,AttributeTypeDefinition>> iterator = 1649 attributeTypes1.entrySet().iterator(); 1650 while (iterator.hasNext()) 1651 { 1652 final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next(); 1653 final OID oid = e.getKey(); 1654 if (! attributeTypes2.containsKey(oid)) 1655 { 1656 reportDifference( 1657 WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get( 1658 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 1659 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 1660 numDifferences, 1661 e.getValue().toString()); 1662 iterator.remove(); 1663 } 1664 } 1665 1666 iterator = attributeTypes2.entrySet().iterator(); 1667 while (iterator.hasNext()) 1668 { 1669 final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next(); 1670 final OID oid = e.getKey(); 1671 if (! attributeTypes1.containsKey(oid)) 1672 { 1673 reportDifference( 1674 WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get( 1675 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 1676 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 1677 numDifferences, 1678 e.getValue().toString()); 1679 iterator.remove(); 1680 } 1681 } 1682 1683 1684 // Any remaining attribute types should exist in both servers. Compare them 1685 // and see if there are any differences between them. 1686 for (final OID oid : attributeTypes1.keySet()) 1687 { 1688 final AttributeTypeDefinition d1 = attributeTypes1.get(oid); 1689 final AttributeTypeDefinition d2 = attributeTypes2.get(oid); 1690 1691 final String identifier = compareNames( 1692 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), 1693 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 1694 1695 compareStringValues( 1696 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1697 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(), 1698 d1.getSuperiorType(), d2.getSuperiorType(), numDifferences); 1699 1700 compareStringValues( 1701 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1702 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(), 1703 d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences); 1704 1705 compareStringValues( 1706 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1707 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_EQUALITY_MR.get(), 1708 d1.getEqualityMatchingRule(), d2.getEqualityMatchingRule(), 1709 numDifferences); 1710 1711 compareStringValues( 1712 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1713 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_ORDERING_MR.get(), 1714 d1.getOrderingMatchingRule(), d2.getOrderingMatchingRule(), 1715 numDifferences); 1716 1717 compareStringValues( 1718 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1719 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUBSTRING_MR.get(), 1720 d1.getSubstringMatchingRule(), d2.getSubstringMatchingRule(), 1721 numDifferences); 1722 1723 compareBooleanValues( 1724 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1725 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_SINGLE_VALUE.get(), 1726 d1.isSingleValued(), d2.isSingleValued(), numDifferences); 1727 1728 compareStringValues( 1729 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1730 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_USAGE.get(), 1731 d1.getUsage().getName(), d2.getUsage().getName(), 1732 numDifferences); 1733 1734 compareBooleanValues( 1735 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1736 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_NO_USER_MOD.get(), 1737 d1.isNoUserModification(), d2.isNoUserModification(), 1738 numDifferences); 1739 1740 compareBooleanValues( 1741 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1742 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_COLLECTIVE.get(), 1743 d1.isCollective(), d2.isCollective(), numDifferences); 1744 1745 compareBooleanValues( 1746 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1747 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 1748 d1.isObsolete(), d2.isObsolete(), numDifferences); 1749 1750 if (! ignoreDescriptions) 1751 { 1752 compareStringValues( 1753 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier, 1754 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 1755 d1.getDescription(), d2.getDescription(), numDifferences); 1756 } 1757 1758 compareExtensions( 1759 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(), 1760 identifier, d1.getExtensions(), d2.getExtensions(), 1761 numDifferences); 1762 } 1763 } 1764 1765 1766 1767 /** 1768 * Retrieves a map of the attribute type definitions contained in the provided 1769 * schema, indexed by OID. 1770 * 1771 * @param schema The schema from which to retrieve the attribute types. It 1772 * must not be {@code null}. 1773 * 1774 * @return A map of the attribute type definitions contained in the provided 1775 * schema. 1776 */ 1777 @NotNull() 1778 private Map<OID,AttributeTypeDefinition> getAttributeTypeMap( 1779 @NotNull final Schema schema) 1780 { 1781 final Map<OID,AttributeTypeDefinition> attributeTypes = new TreeMap<>(); 1782 for (final AttributeTypeDefinition d : schema.getAttributeTypes()) 1783 { 1784 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 1785 { 1786 attributeTypes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 1787 } 1788 } 1789 1790 return attributeTypes; 1791 } 1792 1793 1794 1795 /** 1796 * Compares the object class definitions contained in the provided schemas. 1797 * 1798 * @param firstServerSchema The schema retrieved from the first server. It 1799 * must not be {@code null}. 1800 * @param secondServerSchema The schema retrieved from the second server. 1801 * It must not be {@code null}. 1802 * @param numDifferences A counter used to keep track of the number of 1803 * differences found between the schemas. It must 1804 * not be {@code null}. 1805 */ 1806 private void compareObjectClasses( 1807 @NotNull final Schema firstServerSchema, 1808 @NotNull final Schema secondServerSchema, 1809 @NotNull final AtomicInteger numDifferences) 1810 { 1811 // Get the object class definitions from each of the schemas. 1812 final Map<OID,ObjectClassDefinition> objectClasses1 = 1813 getObjectClassMap(firstServerSchema); 1814 final Map<OID,ObjectClassDefinition> objectClasses2 = 1815 getObjectClassMap(secondServerSchema); 1816 1817 1818 // Identify object classes that exist in one server but not another. If 1819 // any are found, then report them and remove them from the set. 1820 Iterator<Map.Entry<OID,ObjectClassDefinition>> iterator = 1821 objectClasses1.entrySet().iterator(); 1822 while (iterator.hasNext()) 1823 { 1824 final Map.Entry<OID,ObjectClassDefinition> e = iterator.next(); 1825 final OID oid = e.getKey(); 1826 if (! objectClasses2.containsKey(oid)) 1827 { 1828 reportDifference( 1829 WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get( 1830 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 1831 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 1832 numDifferences, 1833 e.getValue().toString()); 1834 iterator.remove(); 1835 } 1836 } 1837 1838 iterator = objectClasses2.entrySet().iterator(); 1839 while (iterator.hasNext()) 1840 { 1841 final Map.Entry<OID,ObjectClassDefinition> e = iterator.next(); 1842 final OID oid = e.getKey(); 1843 if (! objectClasses1.containsKey(oid)) 1844 { 1845 reportDifference( 1846 WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get( 1847 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 1848 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 1849 numDifferences, 1850 e.getValue().toString()); 1851 iterator.remove(); 1852 } 1853 } 1854 1855 1856 // Any remaining object classes should exist in both servers. Compare them 1857 // and see if there are any differences between them. 1858 for (final OID oid : objectClasses1.keySet()) 1859 { 1860 final ObjectClassDefinition d1 = objectClasses1.get(oid); 1861 final ObjectClassDefinition d2 = objectClasses2.get(oid); 1862 1863 final String identifier = compareNames( 1864 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), 1865 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 1866 1867 compareStringArrayValues( 1868 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1869 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(), 1870 d1.getSuperiorClasses(), d2.getSuperiorClasses(), numDifferences); 1871 1872 compareStringArrayValues( 1873 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1874 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(), 1875 d1.getRequiredAttributes(), d2.getRequiredAttributes(), 1876 numDifferences); 1877 1878 compareStringArrayValues( 1879 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1880 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(), 1881 d1.getOptionalAttributes(), d2.getOptionalAttributes(), 1882 numDifferences); 1883 1884 final String oc1Type = (d1.getObjectClassType() == null) 1885 ? null 1886 : d1.getObjectClassType().getName(); 1887 final String oc2Type = (d2.getObjectClassType() == null) 1888 ? null 1889 : d2.getObjectClassType().getName(); 1890 compareStringValues( 1891 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1892 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OBJECT_CLASS_TYPE.get(), 1893 oc1Type, oc2Type, numDifferences); 1894 1895 compareBooleanValues( 1896 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1897 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 1898 d1.isObsolete(), d2.isObsolete(), numDifferences); 1899 1900 if (! ignoreDescriptions) 1901 { 1902 compareStringValues( 1903 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier, 1904 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 1905 d1.getDescription(), d2.getDescription(), numDifferences); 1906 } 1907 1908 compareExtensions( 1909 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), 1910 identifier, d1.getExtensions(), d2.getExtensions(), 1911 numDifferences); 1912 } 1913 } 1914 1915 1916 1917 /** 1918 * Retrieves a map of the object class definitions contained in the provided 1919 * schema, indexed by OID. 1920 * 1921 * @param schema The schema from which to retrieve the object classes. It 1922 * must not be {@code null}. 1923 * 1924 * @return A map of the object class definitions contained in the provided 1925 * schema. 1926 */ 1927 @NotNull() 1928 private Map<OID,ObjectClassDefinition> getObjectClassMap( 1929 @NotNull final Schema schema) 1930 { 1931 final Map<OID,ObjectClassDefinition> objectClasses = new TreeMap<>(); 1932 for (final ObjectClassDefinition d : schema.getObjectClasses()) 1933 { 1934 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 1935 { 1936 objectClasses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 1937 } 1938 } 1939 1940 return objectClasses; 1941 } 1942 1943 1944 1945 /** 1946 * Compares the DIT content rule definitions contained in the provided 1947 * schemas. 1948 * 1949 * @param firstServerSchema The schema retrieved from the first server. It 1950 * must not be {@code null}. 1951 * @param secondServerSchema The schema retrieved from the second server. 1952 * It must not be {@code null}. 1953 * @param numDifferences A counter used to keep track of the number of 1954 * differences found between the schemas. It must 1955 * not be {@code null}. 1956 */ 1957 private void compareDITContentRules( 1958 @NotNull final Schema firstServerSchema, 1959 @NotNull final Schema secondServerSchema, 1960 @NotNull final AtomicInteger numDifferences) 1961 { 1962 // Get the DIT content rule definitions from each of the schemas. 1963 final Map<OID,DITContentRuleDefinition> ditContentRules1 = 1964 getDITContentRuleMap(firstServerSchema); 1965 final Map<OID,DITContentRuleDefinition> ditContentRules2 = 1966 getDITContentRuleMap(secondServerSchema); 1967 1968 1969 // Identify DIT content rules that exist in one server but not another. If 1970 // any are found, then report them and remove them from the set. 1971 Iterator<Map.Entry<OID,DITContentRuleDefinition>> iterator = 1972 ditContentRules1.entrySet().iterator(); 1973 while (iterator.hasNext()) 1974 { 1975 final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next(); 1976 final OID oid = e.getKey(); 1977 if (! ditContentRules2.containsKey(oid)) 1978 { 1979 reportDifference( 1980 WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get( 1981 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 1982 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 1983 numDifferences, 1984 e.getValue().toString()); 1985 iterator.remove(); 1986 } 1987 } 1988 1989 iterator = ditContentRules2.entrySet().iterator(); 1990 while (iterator.hasNext()) 1991 { 1992 final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next(); 1993 final OID oid = e.getKey(); 1994 if (! ditContentRules1.containsKey(oid)) 1995 { 1996 reportDifference( 1997 WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get( 1998 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 1999 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2000 numDifferences, 2001 e.getValue().toString()); 2002 iterator.remove(); 2003 } 2004 } 2005 2006 2007 // Any remaining DIT content rules should exist in both servers. Compare 2008 // them and see if there are any differences between them. 2009 for (final OID oid : ditContentRules1.keySet()) 2010 { 2011 final DITContentRuleDefinition d1 = ditContentRules1.get(oid); 2012 final DITContentRuleDefinition d2 = ditContentRules2.get(oid); 2013 2014 final String identifier = compareNames( 2015 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), 2016 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 2017 2018 compareStringArrayValues( 2019 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier, 2020 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(), 2021 d1.getRequiredAttributes(), d2.getRequiredAttributes(), 2022 numDifferences); 2023 2024 compareStringArrayValues( 2025 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier, 2026 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(), 2027 d1.getOptionalAttributes(), d2.getOptionalAttributes(), 2028 numDifferences); 2029 2030 compareStringArrayValues( 2031 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier, 2032 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_PROHIBITED_ATTRIBUTE.get(), 2033 d1.getProhibitedAttributes(), d2.getProhibitedAttributes(), 2034 numDifferences); 2035 2036 compareStringArrayValues( 2037 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier, 2038 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_AUXILIARY_CLASS.get(), 2039 d1.getAuxiliaryClasses(), d2.getAuxiliaryClasses(), numDifferences); 2040 2041 compareBooleanValues( 2042 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier, 2043 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 2044 d1.isObsolete(), d2.isObsolete(), numDifferences); 2045 2046 if (! ignoreDescriptions) 2047 { 2048 compareStringValues( 2049 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), 2050 identifier, 2051 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 2052 d1.getDescription(), d2.getDescription(), numDifferences); 2053 } 2054 2055 compareExtensions( 2056 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), 2057 identifier, d1.getExtensions(), d2.getExtensions(), 2058 numDifferences); 2059 } 2060 } 2061 2062 2063 2064 /** 2065 * Retrieves a map of the DIT content rule definitions contained in the 2066 * provided schema, indexed by OID. 2067 * 2068 * @param schema The schema from which to retrieve the DIT content rules. 2069 * It must not be {@code null}. 2070 * 2071 * @return A map of the DIT content rule definitions contained in the 2072 * provided schema. 2073 */ 2074 @NotNull() 2075 private Map<OID,DITContentRuleDefinition> getDITContentRuleMap( 2076 @NotNull final Schema schema) 2077 { 2078 final Map<OID,DITContentRuleDefinition> ditContentRules = new TreeMap<>(); 2079 for (final DITContentRuleDefinition d : schema.getDITContentRules()) 2080 { 2081 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 2082 { 2083 ditContentRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 2084 } 2085 } 2086 2087 return ditContentRules; 2088 } 2089 2090 2091 2092 /** 2093 * Compares the DIT structure rule definitions contained in the provided 2094 * schemas. 2095 * 2096 * @param firstServerSchema The schema retrieved from the first server. It 2097 * must not be {@code null}. 2098 * @param secondServerSchema The schema retrieved from the second server. 2099 * It must not be {@code null}. 2100 * @param numDifferences A counter used to keep track of the number of 2101 * differences found between the schemas. It must 2102 * not be {@code null}. 2103 */ 2104 private void compareDITStructureRules( 2105 @NotNull final Schema firstServerSchema, 2106 @NotNull final Schema secondServerSchema, 2107 @NotNull final AtomicInteger numDifferences) 2108 { 2109 // Get the DIT structure rule definitions from each of the schemas. 2110 final Map<Integer,DITStructureRuleDefinition> ditStructureRules1 = 2111 getDITStructureRuleMap(firstServerSchema); 2112 final Map<Integer,DITStructureRuleDefinition> ditStructureRules2 = 2113 getDITStructureRuleMap(secondServerSchema); 2114 2115 2116 // Identify DIT structure rules that exist in one server but not another. 2117 // If any are found, then report them and remove them from the set. 2118 Iterator<Map.Entry<Integer,DITStructureRuleDefinition>> iterator = 2119 ditStructureRules1.entrySet().iterator(); 2120 while (iterator.hasNext()) 2121 { 2122 final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next(); 2123 final Integer id = e.getKey(); 2124 if (! ditStructureRules2.containsKey(id)) 2125 { 2126 reportDifference( 2127 WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get( 2128 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2129 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2130 numDifferences, 2131 e.getValue().toString()); 2132 iterator.remove(); 2133 } 2134 } 2135 2136 iterator = ditStructureRules2.entrySet().iterator(); 2137 while (iterator.hasNext()) 2138 { 2139 final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next(); 2140 final Integer oid = e.getKey(); 2141 if (! ditStructureRules1.containsKey(oid)) 2142 { 2143 reportDifference( 2144 WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get( 2145 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2146 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2147 numDifferences, 2148 e.getValue().toString()); 2149 iterator.remove(); 2150 } 2151 } 2152 2153 2154 // Any remaining DIT structure rules should exist in both servers. Compare 2155 // them and see if there are any differences between them. 2156 for (final Integer id : ditStructureRules1.keySet()) 2157 { 2158 final DITStructureRuleDefinition d1 = ditStructureRules1.get(id); 2159 final DITStructureRuleDefinition d2 = ditStructureRules2.get(id); 2160 2161 final String identifier = compareNames( 2162 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2163 id.toString(), d1.getNames(), d2.getNames(), numDifferences); 2164 2165 compareStringValues( 2166 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2167 identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME_FORM.get(), 2168 d1.getNameFormID(), d2.getNameFormID(), numDifferences); 2169 2170 final String[] superiorRuleIDs1 = 2171 intArrayToStringArray(d1.getSuperiorRuleIDs()); 2172 final String[] superiorRuleIDs2 = 2173 intArrayToStringArray(d2.getSuperiorRuleIDs()); 2174 compareStringArrayValues( 2175 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2176 identifier, 2177 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_RULE_ID.get(), 2178 superiorRuleIDs1, superiorRuleIDs2, numDifferences); 2179 2180 compareBooleanValues( 2181 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2182 identifier, INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 2183 d1.isObsolete(), d2.isObsolete(), numDifferences); 2184 2185 if (! ignoreDescriptions) 2186 { 2187 compareStringValues( 2188 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2189 identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 2190 d1.getDescription(), d2.getDescription(), numDifferences); 2191 } 2192 2193 compareExtensions( 2194 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(), 2195 identifier, d1.getExtensions(), d2.getExtensions(), 2196 numDifferences); 2197 } 2198 } 2199 2200 2201 2202 /** 2203 * Retrieves a map of the DIT structure rule definitions contained in the 2204 * provided schema, indexed by rule ID. 2205 * 2206 * @param schema The schema from which to retrieve the DIT structure rules. 2207 * It must not be {@code null}. 2208 * 2209 * @return A map of the DIT structure rule definitions contained in the 2210 * provided schema. 2211 */ 2212 @NotNull() 2213 private Map<Integer,DITStructureRuleDefinition> getDITStructureRuleMap( 2214 @NotNull final Schema schema) 2215 { 2216 final Map<Integer,DITStructureRuleDefinition> ditStructureRules = 2217 new TreeMap<>(); 2218 for (final DITStructureRuleDefinition d : schema.getDITStructureRules()) 2219 { 2220 if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS, 2221 d.getExtensions())) 2222 { 2223 ditStructureRules.put(d.getRuleID(), d); 2224 } 2225 } 2226 2227 return ditStructureRules; 2228 } 2229 2230 2231 2232 /** 2233 * Converts the provided integer array to a string array in which each element 2234 * is the string representation of the corresponding element in the provided 2235 * integer array. 2236 * 2237 * @param intArray The integer array to convert to a string array. It must 2238 * not be {@code null}, but may be empty. 2239 * 2240 * @return A string array in which each element is the string representation 2241 * of the corresponding element in the provided integer array. 2242 */ 2243 @NotNull() 2244 private static String[] intArrayToStringArray(@NotNull final int[] intArray) 2245 { 2246 final String[] stringArray = new String[intArray.length]; 2247 for (int i=0; i < intArray.length; i++) 2248 { 2249 stringArray[i] = String.valueOf(intArray[i]); 2250 } 2251 2252 return stringArray; 2253 } 2254 2255 2256 2257 /** 2258 * Compares the name form definitions contained in the provided schemas. 2259 * 2260 * @param firstServerSchema The schema retrieved from the first server. It 2261 * must not be {@code null}. 2262 * @param secondServerSchema The schema retrieved from the second server. 2263 * It must not be {@code null}. 2264 * @param numDifferences A counter used to keep track of the number of 2265 * differences found between the schemas. It must 2266 * not be {@code null}. 2267 */ 2268 private void compareNameForms( 2269 @NotNull final Schema firstServerSchema, 2270 @NotNull final Schema secondServerSchema, 2271 @NotNull final AtomicInteger numDifferences) 2272 { 2273 // Get the name form definitions from each of the schemas. 2274 final Map<OID,NameFormDefinition> nameForms1 = 2275 getNameFormMap(firstServerSchema); 2276 final Map<OID,NameFormDefinition> nameForms2 = 2277 getNameFormMap(secondServerSchema); 2278 2279 2280 // Identify name forms that exist in one server but not another. If 2281 // any are found, then report them and remove them from the set. 2282 Iterator<Map.Entry<OID,NameFormDefinition>> iterator = 2283 nameForms1.entrySet().iterator(); 2284 while (iterator.hasNext()) 2285 { 2286 final Map.Entry<OID,NameFormDefinition> e = iterator.next(); 2287 final OID oid = e.getKey(); 2288 if (! nameForms2.containsKey(oid)) 2289 { 2290 reportDifference( 2291 WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get( 2292 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2293 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2294 numDifferences, 2295 e.getValue().toString()); 2296 iterator.remove(); 2297 } 2298 } 2299 2300 iterator = nameForms2.entrySet().iterator(); 2301 while (iterator.hasNext()) 2302 { 2303 final Map.Entry<OID,NameFormDefinition> e = iterator.next(); 2304 final OID oid = e.getKey(); 2305 if (! nameForms1.containsKey(oid)) 2306 { 2307 reportDifference( 2308 WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get( 2309 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2310 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2311 numDifferences, 2312 e.getValue().toString()); 2313 iterator.remove(); 2314 } 2315 } 2316 2317 2318 // Any remaining name forms should exist in both servers. Compare them and 2319 // see if there are any differences between them. 2320 for (final OID oid : nameForms1.keySet()) 2321 { 2322 final NameFormDefinition d1 = nameForms1.get(oid); 2323 final NameFormDefinition d2 = nameForms2.get(oid); 2324 2325 final String identifier = compareNames( 2326 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), 2327 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 2328 2329 compareStringValues( 2330 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier, 2331 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_STRUCTURAL_CLASS.get(), 2332 d1.getStructuralClass(), d2.getStructuralClass(), numDifferences); 2333 2334 compareStringArrayValues( 2335 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier, 2336 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(), 2337 d1.getRequiredAttributes(), d2.getRequiredAttributes(), 2338 numDifferences); 2339 2340 compareStringArrayValues( 2341 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier, 2342 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(), 2343 d1.getOptionalAttributes(), d2.getOptionalAttributes(), 2344 numDifferences); 2345 2346 compareBooleanValues( 2347 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier, 2348 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 2349 d1.isObsolete(), d2.isObsolete(), numDifferences); 2350 2351 if (! ignoreDescriptions) 2352 { 2353 compareStringValues( 2354 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier, 2355 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 2356 d1.getDescription(), d2.getDescription(), numDifferences); 2357 } 2358 2359 compareExtensions( 2360 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), 2361 identifier, d1.getExtensions(), d2.getExtensions(), 2362 numDifferences); 2363 } 2364 } 2365 2366 2367 2368 /** 2369 * Retrieves a map of the name form definitions contained in the provided 2370 * schema, indexed by OID. 2371 * 2372 * @param schema The schema from which to retrieve the name forms. It must 2373 * not be {@code null}. 2374 * 2375 * @return A map of the name form definitions contained in the provided 2376 * schema. 2377 */ 2378 @NotNull() 2379 private Map<OID,NameFormDefinition> getNameFormMap( 2380 @NotNull final Schema schema) 2381 { 2382 final Map<OID,NameFormDefinition> nameForms = new TreeMap<>(); 2383 for (final NameFormDefinition d : schema.getNameForms()) 2384 { 2385 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 2386 { 2387 nameForms.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 2388 } 2389 } 2390 2391 return nameForms; 2392 } 2393 2394 2395 2396 /** 2397 * Compares the matching rule use definitions contained in the provided 2398 * schemas. 2399 * 2400 * @param firstServerSchema The schema retrieved from the first server. It 2401 * must not be {@code null}. 2402 * @param secondServerSchema The schema retrieved from the second server. 2403 * It must not be {@code null}. 2404 * @param numDifferences A counter used to keep track of the number of 2405 * differences found between the schemas. It must 2406 * not be {@code null}. 2407 */ 2408 private void compareMatchingRuleUses( 2409 @NotNull final Schema firstServerSchema, 2410 @NotNull final Schema secondServerSchema, 2411 @NotNull final AtomicInteger numDifferences) 2412 { 2413 // Get the matching rule use definitions from each of the schemas. 2414 final Map<OID,MatchingRuleUseDefinition> matchingRuleUses1 = 2415 getMatchingRuleUseMap(firstServerSchema); 2416 final Map<OID,MatchingRuleUseDefinition> matchingRuleUses2 = 2417 getMatchingRuleUseMap(secondServerSchema); 2418 2419 2420 // Identify matching rule uses that exist in one server but not another. If 2421 // any are found, then report them and remove them from the set. 2422 Iterator<Map.Entry<OID,MatchingRuleUseDefinition>> iterator = 2423 matchingRuleUses1.entrySet().iterator(); 2424 while (iterator.hasNext()) 2425 { 2426 final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next(); 2427 final OID oid = e.getKey(); 2428 if (! matchingRuleUses2.containsKey(oid)) 2429 { 2430 reportDifference( 2431 WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get( 2432 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2433 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2434 numDifferences, 2435 e.getValue().toString()); 2436 iterator.remove(); 2437 } 2438 } 2439 2440 iterator = matchingRuleUses2.entrySet().iterator(); 2441 while (iterator.hasNext()) 2442 { 2443 final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next(); 2444 final OID oid = e.getKey(); 2445 if (! matchingRuleUses1.containsKey(oid)) 2446 { 2447 reportDifference( 2448 WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get( 2449 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2450 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2451 numDifferences, 2452 e.getValue().toString()); 2453 iterator.remove(); 2454 } 2455 } 2456 2457 2458 // Any remaining matching rule uses should exist in both servers. Compare 2459 // them and see if there are any differences between them. 2460 for (final OID oid : matchingRuleUses1.keySet()) 2461 { 2462 final MatchingRuleUseDefinition d1 = matchingRuleUses1.get(oid); 2463 final MatchingRuleUseDefinition d2 = matchingRuleUses2.get(oid); 2464 2465 final String identifier = compareNames( 2466 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), 2467 oid.toString(), d1.getNames(), d2.getNames(), numDifferences); 2468 2469 compareStringArrayValues( 2470 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier, 2471 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_APPLICABLE_ATTRIBUTE.get(), 2472 d1.getApplicableAttributeTypes(), d2.getApplicableAttributeTypes(), 2473 numDifferences); 2474 2475 compareBooleanValues( 2476 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier, 2477 INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(), 2478 d1.isObsolete(), d2.isObsolete(), numDifferences); 2479 2480 if (! ignoreDescriptions) 2481 { 2482 compareStringValues( 2483 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier, 2484 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(), 2485 d1.getDescription(), d2.getDescription(), numDifferences); 2486 } 2487 2488 compareExtensions( 2489 INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), 2490 identifier, d1.getExtensions(), d2.getExtensions(), 2491 numDifferences); 2492 } 2493 } 2494 2495 2496 2497 /** 2498 * Retrieves a map of the matching rule use definitions contained in the 2499 * provided schema, indexed by OID. 2500 * 2501 * @param schema The schema from which to retrieve the matching rule uses. 2502 * It must not be {@code null}. 2503 * 2504 * @return A map of the matching rule use definitions contained in the 2505 * provided schema. 2506 */ 2507 @NotNull() 2508 private Map<OID,MatchingRuleUseDefinition> getMatchingRuleUseMap( 2509 @NotNull final Schema schema) 2510 { 2511 final Map<OID,MatchingRuleUseDefinition> matchingRuleUses = new TreeMap<>(); 2512 for (final MatchingRuleUseDefinition d : schema.getMatchingRuleUses()) 2513 { 2514 if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions())) 2515 { 2516 matchingRuleUses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d); 2517 } 2518 } 2519 2520 return matchingRuleUses; 2521 } 2522 2523 2524 2525 /** 2526 * Indicates whether to include a schema element with the given name and set 2527 * of extensions. 2528 * 2529 * @param names The set of names for the schema element. It must not 2530 * be {@code null}, but may be empty. 2531 * @param extensions The set of extensions for the schema element. It must 2532 * not be {@code null}, but may be empty. 2533 * 2534 * @return {@code true} if an element with the given names and set of 2535 * extensions should be included, or {@code false} if not. 2536 */ 2537 private boolean includeBasedOnNameAndExtensions( 2538 @NotNull final String[] names, 2539 @NotNull final Map<String,String[]> extensions) 2540 { 2541 if (includeOrExcludeBasedOnName && (names.length > 0)) 2542 { 2543 boolean includeFound = false; 2544 for (final String name : names) 2545 { 2546 final String lowerName = StaticUtils.toLowerCase(name); 2547 for (final String excludePrefix : excludeNamePrefixes) 2548 { 2549 if (lowerName.startsWith(excludePrefix)) 2550 { 2551 return false; 2552 } 2553 } 2554 2555 if (! includeNamePrefixes.isEmpty()) 2556 { 2557 for (final String includePrefix : includeNamePrefixes) 2558 { 2559 if (lowerName.startsWith(includePrefix)) 2560 { 2561 includeFound = true; 2562 break; 2563 } 2564 } 2565 } 2566 } 2567 2568 if ((! includeNamePrefixes.isEmpty()) && (! includeFound)) 2569 { 2570 return false; 2571 } 2572 } 2573 2574 2575 if (includeOrExcludeBasedOnExtensions && (! extensions.isEmpty())) 2576 { 2577 boolean includeFound = false; 2578 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 2579 { 2580 final String lowerName = StaticUtils.toLowerCase(e.getKey()); 2581 final String[] values = e.getValue(); 2582 final String[] lowerValues = new String[values.length]; 2583 for (int i=0; i < values.length; i++) 2584 { 2585 lowerValues[i] = StaticUtils.toLowerCase(values[i]); 2586 } 2587 2588 final List<String> excludeValues = 2589 excludeExtensionValues.get(lowerName); 2590 if (excludeValues != null) 2591 { 2592 for (final String lowerValue : lowerValues) 2593 { 2594 if (excludeValues.contains(lowerValue)) 2595 { 2596 return false; 2597 } 2598 } 2599 } 2600 2601 final List<String> includeValues = 2602 includeExtensionValues.get(lowerName); 2603 if (includeValues != null) 2604 { 2605 for (final String lowerValue : lowerValues) 2606 { 2607 if (includeValues.contains(lowerValue)) 2608 { 2609 includeFound = true; 2610 break; 2611 } 2612 } 2613 } 2614 } 2615 2616 if ((! includeExtensionValues.isEmpty()) && (! includeFound)) 2617 { 2618 return false; 2619 } 2620 } 2621 2622 2623 return true; 2624 } 2625 2626 2627 2628 /** 2629 * Reports a difference between schema elements. 2630 * 2631 * @param message The message to display with information about 2632 * the difference. It must not be {@code null}. 2633 * @param numDifferences A counter used to keep track of the number of 2634 * differences found between the schemas. It must 2635 * not be {@code null}. 2636 * @param additionalStrings A set of additional strings that should also be 2637 * displayed, in addition to the provided message. 2638 * Each additional string will be presented on its 2639 * own line without any wrapping. It must not be 2640 * {@code null}, but may be empty. 2641 */ 2642 private void reportDifference(@NotNull final String message, 2643 @NotNull final AtomicInteger numDifferences, 2644 @NotNull final String... additionalStrings) 2645 { 2646 wrapErr(0, WRAP_COLUMN, message); 2647 for (final String additionalString : additionalStrings) 2648 { 2649 err(additionalString); 2650 } 2651 err(); 2652 numDifferences.incrementAndGet(); 2653 } 2654 2655 2656 2657 /** 2658 * Identifies differences between string values for two schema elements. 2659 * 2660 * @param elementTypeName A name for the type of schema element being 2661 * compared. It must not be {@code null}. 2662 * @param elementIdentifier An identifier (e.g., the name or OID) for the 2663 * schema element for which to make the 2664 * determination. It must not be {@code null}. 2665 * @param stringDescriptor A descriptor for the string values being 2666 * compared. 2667 * @param string1 The string value from the first schema element. 2668 * It may be {@code null} if the element does not 2669 * contain a value for the associated field. 2670 * @param string2 The string value from the first second element. 2671 * It may be {@code null} if the element does not 2672 * contain a value for the associated field. 2673 * @param numDifferences A counter used to keep track of the number of 2674 * differences found between the schemas. It must 2675 * not be {@code null}. 2676 */ 2677 private void compareStringValues(@NotNull final String elementTypeName, 2678 @NotNull final String elementIdentifier, 2679 @NotNull final String stringDescriptor, 2680 @Nullable final String string1, 2681 @Nullable final String string2, 2682 @NotNull final AtomicInteger numDifferences) 2683 { 2684 if (string1 == null) 2685 { 2686 if (string2 != null) 2687 { 2688 reportDifference( 2689 WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get( 2690 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2691 elementTypeName, elementIdentifier, stringDescriptor, 2692 string2, INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2693 numDifferences); 2694 } 2695 } 2696 else if (string2 == null) 2697 { 2698 reportDifference( 2699 WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get( 2700 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2701 elementTypeName, elementIdentifier, stringDescriptor, 2702 string1, INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2703 numDifferences); 2704 } 2705 else if (! string1.equalsIgnoreCase(string2)) 2706 { 2707 reportDifference( 2708 WARN_COMPARE_SCHEMA_STRING_DIFFERENT_BETWEEN_SERVERS.get( 2709 elementTypeName, elementIdentifier, stringDescriptor, string1, 2710 string2), 2711 numDifferences); 2712 } 2713 } 2714 2715 2716 2717 /** 2718 * Identifies differences between the sets of string arrays for two schema 2719 * elements. 2720 * 2721 * @param elementTypeName A name for the type of schema element being 2722 * compared. It must not be {@code null}. 2723 * @param elementIdentifier An identifier (e.g., the name or OID) for the 2724 * schema element for which to make the 2725 * determination. It must not be {@code null}. 2726 * @param stringDescriptor A descriptor for the string values being 2727 * compared. 2728 * @param array1 The array of values for the target field from 2729 * the element in the first schema. It must not be 2730 * {@code null}, but may be empty. 2731 * @param array2 The array of values for the target field from 2732 * the element in the second schema. It must not 2733 * be {@code null}, but may be empty. 2734 * @param numDifferences A counter used to keep track of the number of 2735 * differences found between the schemas. It must 2736 * not be {@code null}. 2737 */ 2738 private void compareStringArrayValues( 2739 @NotNull final String elementTypeName, 2740 @NotNull final String elementIdentifier, 2741 @NotNull final String stringDescriptor, 2742 @NotNull final String[] array1, 2743 @NotNull final String[] array2, 2744 @NotNull final AtomicInteger numDifferences) 2745 { 2746 if (array1.length == 0) 2747 { 2748 switch (array2.length) 2749 { 2750 case 0: 2751 // The element doesn't have any names in either of the servers, so 2752 // there is no difference. 2753 break; 2754 case 1: 2755 // The element has names in the second server, but not in the first. 2756 reportDifference( 2757 WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get( 2758 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2759 elementTypeName, elementIdentifier, stringDescriptor, 2760 array2[0], INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2761 numDifferences); 2762 break; 2763 default: 2764 reportDifference( 2765 WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get( 2766 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2767 elementTypeName, elementIdentifier, stringDescriptor, 2768 Arrays.toString(array2), 2769 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2770 numDifferences); 2771 break; 2772 } 2773 } 2774 else if (array2.length == 0) 2775 { 2776 // The element has names in the first server, but not in the second. 2777 if (array1.length == 1) 2778 { 2779 reportDifference( 2780 WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get( 2781 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2782 elementTypeName, elementIdentifier, stringDescriptor, 2783 array1[0], INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2784 numDifferences); 2785 } 2786 else 2787 { 2788 reportDifference( 2789 WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get( 2790 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2791 elementTypeName, elementIdentifier, stringDescriptor, 2792 Arrays.toString(array1), 2793 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2794 numDifferences); 2795 } 2796 } 2797 else 2798 { 2799 // The element has names in both servers. See if there are any 2800 // differences between them. 2801 final Map<String,String> n1 = getNameMap(array1); 2802 final Map<String,String> n2 = getNameMap(array2); 2803 for (final Map.Entry<String,String> e : n1.entrySet()) 2804 { 2805 final String lowerName = e.getKey(); 2806 if (n2.remove(lowerName) == null) 2807 { 2808 reportDifference( 2809 WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get( 2810 elementTypeName, elementIdentifier, stringDescriptor, 2811 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2812 e.getValue(), 2813 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2814 numDifferences); 2815 } 2816 } 2817 2818 for (final String nameOnlyInServer2 : n2.values()) 2819 { 2820 reportDifference( 2821 WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get( 2822 elementTypeName, elementIdentifier, stringDescriptor, 2823 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2824 nameOnlyInServer2, 2825 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2826 numDifferences); 2827 } 2828 } 2829 } 2830 2831 2832 2833 /** 2834 * Identifies differences between the sets of names for two schema elements. 2835 * 2836 * @param elementTypeName A name for the type of schema element being 2837 * compared. It must not be {@code null}. 2838 * @param elementIdentifier An identifier (e.g., the name or OID) for the 2839 * schema element for which to make the 2840 * determination. It must not be {@code null}. 2841 * @param names1 The set of names for the element from the first 2842 * schema. It must not be {@code null}, but may be 2843 * empty. 2844 * @param names2 The set of names for the element from the second 2845 * schema. It must not be {@code null}, but may be 2846 * empty. 2847 * @param numDifferences A counter used to keep track of the number of 2848 * differences found between the schemas. It must 2849 * not be {@code null}. 2850 * 2851 * @return The identifier string that should be used to identify the 2852 * associated schema element. If both sets of names are non-empty 2853 * and have the same first element, then that name will be used as 2854 * the identifier. Otherwise, the provided identifier will be used. 2855 */ 2856 @NotNull() 2857 private String compareNames(@NotNull final String elementTypeName, 2858 @NotNull final String elementIdentifier, 2859 @NotNull final String[] names1, 2860 @NotNull final String[] names2, 2861 @NotNull final AtomicInteger numDifferences) 2862 { 2863 compareStringArrayValues(elementTypeName, elementIdentifier, 2864 INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME.get(), names1, names2, 2865 numDifferences); 2866 2867 2868 // Identify the best identifier to use for the schema element going forward. 2869 if ((names1.length > 0) && (names2.length > 0) && 2870 (names1[0].equalsIgnoreCase(names2[0]))) 2871 { 2872 return names1[0]; 2873 } 2874 else 2875 { 2876 return elementIdentifier; 2877 } 2878 } 2879 2880 2881 2882 /** 2883 * Identifies difference between boolean values for two schema elements. 2884 * 2885 * @param elementTypeName A name for the type of schema element being 2886 * compared. It must not be {@code null}. 2887 * @param elementIdentifier An identifier (e.g., the name or OID) for the 2888 * schema element for which to make the 2889 * determination. It must not be {@code null}. 2890 * @param booleanFieldName The name of the Boolean field being compared. 2891 * @param value1 The Boolean value from the first schema element. 2892 * @param value2 The Boolean value from the second schema 2893 * element. 2894 * @param numDifferences A counter used to keep track of the number of 2895 * differences found between the schemas. It must 2896 * not be {@code null}. 2897 */ 2898 private void compareBooleanValues(@NotNull final String elementTypeName, 2899 @NotNull final String elementIdentifier, 2900 @NotNull final String booleanFieldName, 2901 final boolean value1, 2902 final boolean value2, 2903 @NotNull final AtomicInteger numDifferences) 2904 { 2905 if (value1 != value2) 2906 { 2907 if (value1) 2908 { 2909 reportDifference( 2910 WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get( 2911 elementTypeName, elementIdentifier, booleanFieldName, 2912 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2913 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2914 numDifferences); 2915 } 2916 else 2917 { 2918 reportDifference( 2919 WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get( 2920 elementTypeName, elementIdentifier, booleanFieldName, 2921 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2922 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 2923 numDifferences); 2924 } 2925 } 2926 } 2927 2928 2929 2930 /** 2931 * Identifies differences between the sets of extensions for two schema 2932 * elements. 2933 * 2934 * @param elementTypeName A name for the type of schema element being 2935 * compared. It must not be {@code null}. 2936 * @param elementIdentifier An identifier (e.g., the name or OID) for the 2937 * schema element for which to make the 2938 * determination. It must not be {@code null}. 2939 * @param extensions1 The set of extensions for the element from the 2940 * first schema. It must not be {@code null}, but 2941 * may be empty. 2942 * @param extensions2 The set of extensions for the element from the 2943 * second schema. It must not be {@code null}, but 2944 * may be empty. 2945 * @param numDifferences A counter used to keep track of the number of 2946 * differences found between the schemas. It must 2947 * not be {@code null}. 2948 */ 2949 private void compareExtensions( 2950 @NotNull final String elementTypeName, 2951 @NotNull final String elementIdentifier, 2952 @NotNull final Map<String,String[]> extensions1, 2953 @NotNull final Map<String,String[]> extensions2, 2954 @NotNull final AtomicInteger numDifferences) 2955 { 2956 if (ignoreExtensions) 2957 { 2958 return; 2959 } 2960 2961 2962 // Convert the extensions into a map of sets so that we can alter the 2963 // contents of both the map and its sets. 2964 final Map<String,Set<String>> e1 = 2965 convertToUpdatableExtensionsMap(extensions1); 2966 final Map<String,Set<String>> e2 = 2967 convertToUpdatableExtensionsMap(extensions2); 2968 2969 2970 // Iterate through the extensions and identify differences between them. 2971 for (final Map.Entry<String,Set<String>> e : e1.entrySet()) 2972 { 2973 final String extensionName = e.getKey(); 2974 final Set<String> extension1Values = e.getValue(); 2975 final Set<String> extension2Values = e2.remove(extensionName); 2976 if (extension2Values == null) 2977 { 2978 reportDifference( 2979 WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get( 2980 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(), 2981 elementTypeName, elementIdentifier, extensionName, 2982 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()), 2983 numDifferences); 2984 } 2985 else if (! extension1Values.equals(extension2Values)) 2986 { 2987 reportDifference( 2988 WARN_COMPARE_SCHEMA_EXTENSION_DIFFERENCE.get( 2989 elementTypeName, elementIdentifier, extensionName), 2990 numDifferences); 2991 } 2992 } 2993 2994 for (final String extensionName : e2.keySet()) 2995 { 2996 reportDifference( 2997 WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get( 2998 INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(), 2999 elementTypeName, elementIdentifier, extensionName, 3000 INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()), 3001 numDifferences); 3002 } 3003 } 3004 3005 3006 3007 /** 3008 * Converts the provided extensions map into an updatable map that associates 3009 * each extension name key with a modifiable set of values rather than an 3010 * array. In addition, all strings will be converted to lowercase for more 3011 * efficient case-insensitive comparison. 3012 * 3013 * @param extensionsMap The map to be converted. It must not be 3014 * {@code null}, but may be empty. 3015 * 3016 * @return A modifiable map that contains the information in the provided map 3017 * in a form that is better suited for comparing extensions between 3018 * two definitions. 3019 */ 3020 @NotNull() 3021 private static Map<String,Set<String>> convertToUpdatableExtensionsMap( 3022 @NotNull final Map<String,String[]> extensionsMap) 3023 { 3024 final Map<String,Set<String>> convertedExtensionsMap = new TreeMap<>(); 3025 for (final Map.Entry<String,String[]> e : extensionsMap.entrySet()) 3026 { 3027 final String lowerExtensionName = StaticUtils.toLowerCase(e.getKey()); 3028 final Set<String> lowerExtensionValues = new TreeSet<>(); 3029 for (final String extensionValue : e.getValue()) 3030 { 3031 lowerExtensionValues.add(StaticUtils.toLowerCase(extensionValue)); 3032 } 3033 3034 convertedExtensionsMap.put(lowerExtensionName, lowerExtensionValues); 3035 } 3036 3037 return convertedExtensionsMap; 3038 } 3039 3040 3041 3042 /** 3043 * Retrieves a modifiable map containing the provided names. The key for each 3044 * entry in the map will be the name in all lowercase, and the value will be 3045 * the name in the case in which it is provided. 3046 * 3047 * @param names The names to include in the resulting map. It must not be 3048 * {@code null}. 3049 * 3050 * @return A modifiable map containing the provided names. 3051 */ 3052 @NotNull() 3053 private static Map<String,String> getNameMap(@NotNull final String[] names) 3054 { 3055 final Map<String,String> m = new TreeMap<>(); 3056 for (final String name : names) 3057 { 3058 m.put(StaticUtils.toLowerCase(name), name); 3059 } 3060 3061 return m; 3062 } 3063 3064 3065 3066 /** 3067 * Logs the provided message to standard error and sets it as the tool 3068 * completion message. 3069 * 3070 * @param message The completion message. It must not be {@code null}. 3071 */ 3072 private void logCompletionError(@NotNull final String message) 3073 { 3074 completionMessageRef.compareAndSet(null, message); 3075 wrapErr(0, WRAP_COLUMN, message); 3076 } 3077 3078 3079 3080 /** 3081 * {@inheritDoc} 3082 */ 3083 @Override() 3084 @NotNull() 3085 public LinkedHashMap<String[],String> getExampleUsages() 3086 { 3087 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 3088 3089 examples.put( 3090 new String[] 3091 { 3092 "--firstHostname", "ds1.example.com", 3093 "--firstPort", "636", 3094 "--firstUseSSL", 3095 "--firstBindDN", "cn=Directory Manager", 3096 "--firstBindPasswordFile", "/path/to/password.txt", 3097 "--secondHostname", "ds2.example.com", 3098 "--secondPort", "636", 3099 "--secondUseSSL", 3100 "--secondBindDN", "cn=Directory Manager", 3101 "--secondBindPasswordFile", "/path/to/password.txt" 3102 }, 3103 INFO_COMPARE_LDAP_SCHEMAS_EXAMPLE.get()); 3104 3105 return examples; 3106 } 3107}