001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.model.schema; 021 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.directory.api.i18n.I18n; 033import org.apache.directory.api.util.Strings; 034 035 036/** 037 * Most schema objects have some common attributes. This class 038 * contains the minimum set of properties exposed by a SchemaObject.<br> 039 * We have 11 types of SchemaObjects : 040 * <ul> 041 * <li> AttributeType</li> 042 * <li> DitCOntentRule</li> 043 * <li> DitStructureRule</li> 044 * <li> LdapComparator (specific to ADS)</li> 045 * <li> LdapSyntaxe</li> 046 * <li> MatchingRule</li> 047 * <li> MatchingRuleUse</li> 048 * <li> NameForm</li> 049 * <li> Normalizer (specific to ADS)</li> 050 * <li> ObjectClass</li> 051 * <li> SyntaxChecker (specific to ADS)</li> 052 * </ul> 053 * <br> 054 * <br> 055 * This class provides accessors and setters for the following attributes, 056 * which are common to all those SchemaObjects : 057 * <ul> 058 * <li>oid : The numeric OID</li> 059 * <li>description : The SchemaObject description</li> 060 * <li>obsolete : Tells if the schema object is obsolete</li> 061 * <li>extensions : The extensions, a key/Values map</li> 062 * <li>schemaObjectType : The SchemaObject type (see upper)</li> 063 * <li>schema : The schema the SchemaObject is associated with (it's an extension). 064 * Can be null</li> 065 * <li>isEnabled : The SchemaObject status (it's related to the schema status)</li> 066 * <li>isReadOnly : Tells if the SchemaObject can be modified or not</li> 067 * </ul> 068 * <br><br> 069 * Some of those attributes are not used by some Schema elements, even if they should 070 * have been used. Here is the list : 071 * <ul> 072 * <li><b>name</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker</li> 073 * <li><b>numericOid</b> : DitStructureRule</li> 074 * <li><b>obsolete</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker</li> 075 * </ul> 076 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 077 */ 078public abstract class AbstractSchemaObject implements SchemaObject, Serializable 079{ 080 /** The serial version UID */ 081 private static final long serialVersionUID = 2L; 082 083 /** The SchemaObject numeric OID */ 084 protected String oid; 085 086 /** The optional names for this SchemaObject */ 087 protected transient List<String> names; 088 089 /** Whether or not this SchemaObject is enabled */ 090 protected boolean isEnabled = true; 091 092 /** Whether or not this SchemaObject is obsolete */ 093 protected boolean isObsolete = false; 094 095 /** A short description of this SchemaObject */ 096 protected String description; 097 098 /** The SchemaObject specification */ 099 protected String specification; 100 101 /** The name of the schema this object is associated with */ 102 protected String schemaName; 103 104 /** The SchemaObjectType */ 105 protected SchemaObjectType objectType; 106 107 /** A map containing the list of supported extensions */ 108 protected transient Map<String, List<String>> extensions; 109 110 /** A locked to avoid modifications when set to true */ 111 protected volatile boolean locked; 112 113 /** The hashcode for this schemaObject */ 114 protected volatile int h = 0; 115 116 117 /** 118 * A constructor for a SchemaObject instance. It must be 119 * invoked by the inherited class. 120 * 121 * @param objectType The SchemaObjectType to create 122 * @param oid the SchemaObject numeric OID 123 */ 124 protected AbstractSchemaObject( SchemaObjectType objectType, String oid ) 125 { 126 this.objectType = objectType; 127 this.oid = oid; 128 extensions = new HashMap<>(); 129 names = new ArrayList<>(); 130 computeHashCode(); 131 } 132 133 134 /** 135 * Constructor used when a generic reusable SchemaObject is assigned an 136 * OID after being instantiated. 137 * 138 * @param objectType The SchemaObjectType to create 139 */ 140 protected AbstractSchemaObject( SchemaObjectType objectType ) 141 { 142 this.objectType = objectType; 143 extensions = new HashMap<>(); 144 names = new ArrayList<>(); 145 } 146 147 148 /** 149 * Gets usually what is the numeric object identifier assigned to this 150 * SchemaObject. All schema objects except for MatchingRuleUses have an OID 151 * assigned specifically to then. A MatchingRuleUse's OID really is the OID 152 * of it's MatchingRule and not specific to the MatchingRuleUse. This 153 * effects how MatchingRuleUse objects are maintained by the system. 154 * 155 * @return an OID for this SchemaObject or its MatchingRule if this 156 * SchemaObject is a MatchingRuleUse object 157 */ 158 @Override 159 public String getOid() 160 { 161 return oid; 162 } 163 164 165 /** 166 * A special method used when renaming an SchemaObject: we may have to 167 * change it's OID 168 * @param oid The new OID 169 */ 170 @Override 171 public void setOid( String oid ) 172 { 173 if ( locked ) 174 { 175 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 176 } 177 178 this.oid = oid; 179 180 computeHashCode(); 181 } 182 183 184 /** 185 * Gets short names for this SchemaObject if any exists for it, otherwise, 186 * returns an empty list. 187 * 188 * @return the names for this SchemaObject 189 */ 190 @Override 191 public List<String> getNames() 192 { 193 if ( names != null ) 194 { 195 return Collections.unmodifiableList( names ); 196 } 197 else 198 { 199 return Collections.emptyList(); 200 } 201 } 202 203 204 /** 205 * Gets the first name in the set of short names for this SchemaObject if 206 * any exists for it. 207 * 208 * @return the first of the names for this SchemaObject or the oid 209 * if one does not exist 210 */ 211 @Override 212 public String getName() 213 { 214 if ( ( names != null ) && !names.isEmpty() ) 215 { 216 return names.get( 0 ); 217 } 218 else 219 { 220 return oid; 221 } 222 } 223 224 225 /** 226 * Add a new name to the list of names for this SchemaObject. The name 227 * is lower cased and trimmed. 228 * 229 * @param namesToAdd The names to add 230 */ 231 @Override 232 public void addName( String... namesToAdd ) 233 { 234 if ( locked ) 235 { 236 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 237 } 238 239 // We must avoid duplicated names, as names are case insensitive 240 Set<String> lowerNames = new HashSet<>(); 241 242 // Fills a set with all the existing names 243 for ( String name : this.names ) 244 { 245 lowerNames.add( Strings.toLowerCaseAscii( name ) ); 246 } 247 248 for ( String name : namesToAdd ) 249 { 250 if ( name != null ) 251 { 252 String lowerName = Strings.toLowerCaseAscii( name ); 253 // Check that the lower cased names is not already present 254 if ( !lowerNames.contains( lowerName ) ) 255 { 256 this.names.add( name ); 257 lowerNames.add( lowerName ); 258 } 259 } 260 } 261 262 computeHashCode(); 263 } 264 265 266 /** 267 * Sets the list of names for this SchemaObject. The names are 268 * lowercased and trimmed. 269 * 270 * @param names The list of names. Can be empty 271 */ 272 @Override 273 public void setNames( List<String> names ) 274 { 275 if ( locked ) 276 { 277 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 278 } 279 280 if ( names == null ) 281 { 282 return; 283 } 284 285 this.names = new ArrayList<>( names.size() ); 286 287 for ( String name : names ) 288 { 289 if ( name != null ) 290 { 291 this.names.add( name ); 292 } 293 } 294 295 computeHashCode(); 296 } 297 298 299 /** 300 * Sets the list of names for this SchemaObject. The names are 301 * lowercased and trimmed. 302 * 303 * @param names The list of names. 304 */ 305 public void setNames( String... names ) 306 { 307 if ( locked ) 308 { 309 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 310 } 311 312 if ( names == null ) 313 { 314 return; 315 } 316 317 this.names.clear(); 318 319 for ( String name : names ) 320 { 321 if ( name != null ) 322 { 323 this.names.add( name ); 324 } 325 } 326 327 computeHashCode(); 328 } 329 330 331 /** 332 * Gets a short description about this SchemaObject. 333 * 334 * @return a short description about this SchemaObject 335 */ 336 @Override 337 public String getDescription() 338 { 339 return description; 340 } 341 342 343 /** 344 * Sets the SchemaObject's description 345 * 346 * @param description The SchemaObject's description 347 */ 348 @Override 349 public void setDescription( String description ) 350 { 351 if ( locked ) 352 { 353 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 354 } 355 356 this.description = description; 357 358 computeHashCode(); 359 } 360 361 362 /** 363 * Gets the SchemaObject specification. 364 * 365 * @return the SchemaObject specification 366 */ 367 @Override 368 public String getSpecification() 369 { 370 return specification; 371 } 372 373 374 /** 375 * Sets the SchemaObject's specification 376 * 377 * @param specification The SchemaObject's specification 378 */ 379 @Override 380 public void setSpecification( String specification ) 381 { 382 if ( locked ) 383 { 384 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 385 } 386 387 this.specification = specification; 388 389 computeHashCode(); 390 } 391 392 393 /** 394 * Tells if this SchemaObject is enabled. 395 * 396 * @return true if the SchemaObject is enabled, or if it depends on 397 * an enabled schema 398 */ 399 @Override 400 public boolean isEnabled() 401 { 402 return isEnabled; 403 } 404 405 406 /** 407 * Tells if this SchemaObject is disabled. 408 * 409 * @return true if the SchemaObject is disabled 410 */ 411 @Override 412 public boolean isDisabled() 413 { 414 return !isEnabled; 415 } 416 417 418 /** 419 * Sets the SchemaObject state, either enabled or disabled. 420 * 421 * @param enabled The current SchemaObject state 422 */ 423 @Override 424 public void setEnabled( boolean enabled ) 425 { 426 if ( locked ) 427 { 428 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 429 } 430 431 isEnabled = enabled; 432 433 computeHashCode(); 434 } 435 436 437 /** 438 * Gets whether or not this SchemaObject has been inactivated. All 439 * SchemaObjects except Syntaxes allow for this parameter within their 440 * definition. For Syntaxes this property should always return false in 441 * which case it is never included in the description. 442 * 443 * @return true if inactive, false if active 444 */ 445 @Override 446 public boolean isObsolete() 447 { 448 return isObsolete; 449 } 450 451 452 /** 453 * Sets the Obsolete flag. 454 * 455 * @param obsolete The Obsolete flag state 456 */ 457 @Override 458 public void setObsolete( boolean obsolete ) 459 { 460 if ( locked ) 461 { 462 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 463 } 464 465 this.isObsolete = obsolete; 466 467 computeHashCode(); 468 } 469 470 471 /** 472 * {@inheritDoc} 473 */ 474 @Override 475 public Map<String, List<String>> getExtensions() 476 { 477 return extensions; 478 } 479 480 481 /** 482 * {@inheritDoc} 483 */ 484 @Override 485 public boolean hasExtension( String extension ) 486 { 487 return extensions.containsKey( Strings.toUpperCaseAscii( extension ) ); 488 } 489 490 491 /** 492 * {@inheritDoc} 493 */ 494 @Override 495 public List<String> getExtension( String extension ) 496 { 497 String name = Strings.toUpperCaseAscii( extension ); 498 499 if ( hasExtension( name ) ) 500 { 501 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 502 { 503 String key = entry.getKey(); 504 505 if ( name.equalsIgnoreCase( key ) ) 506 { 507 return entry.getValue(); 508 } 509 } 510 } 511 512 return null; 513 } 514 515 516 /** 517 * Add an extension with its values 518 * @param key The extension key 519 * @param values The associated values 520 */ 521 @Override 522 public void addExtension( String key, String... values ) 523 { 524 if ( locked ) 525 { 526 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 527 } 528 529 List<String> valueList = new ArrayList<>(); 530 531 for ( String value : values ) 532 { 533 valueList.add( value ); 534 } 535 536 extensions.put( Strings.toUpperCaseAscii( key ), valueList ); 537 538 computeHashCode(); 539 } 540 541 542 /** 543 * Add an extension with its values 544 * @param key The extension key 545 * @param values The associated values 546 */ 547 @Override 548 public void addExtension( String key, List<String> values ) 549 { 550 if ( locked ) 551 { 552 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 553 } 554 555 extensions.put( Strings.toUpperCaseAscii( key ), values ); 556 557 computeHashCode(); 558 } 559 560 561 /** 562 * Add an extensions with their values. (Actually do a copy) 563 * 564 * @param extensions The extensions map 565 */ 566 @Override 567 public void setExtensions( Map<String, List<String>> extensions ) 568 { 569 if ( locked ) 570 { 571 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 572 } 573 574 if ( extensions != null ) 575 { 576 this.extensions = new HashMap<>(); 577 578 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 579 { 580 List<String> values = new ArrayList<>(); 581 582 for ( String value : entry.getValue() ) 583 { 584 values.add( value ); 585 } 586 587 this.extensions.put( Strings.toUpperCaseAscii( entry.getKey() ), values ); 588 } 589 590 computeHashCode(); 591 } 592 } 593 594 595 /** 596 * The SchemaObject type : 597 * <ul> 598 * <li> AttributeType 599 * <li> DitCOntentRule 600 * <li> DitStructureRule 601 * <li> LdapComparator (specific to ADS) 602 * <li> LdapSyntaxe 603 * <li> MatchingRule 604 * <li> MatchingRuleUse 605 * <li> NameForm 606 * <li> Normalizer (specific to ADS) 607 * <li> ObjectClass 608 * <li> SyntaxChecker (specific to ADS) 609 * </ul> 610 * 611 * @return the SchemaObject type 612 */ 613 @Override 614 public SchemaObjectType getObjectType() 615 { 616 return objectType; 617 } 618 619 620 /** 621 * Gets the name of the schema this SchemaObject is associated with. 622 * 623 * @return the name of the schema associated with this schemaObject 624 */ 625 @Override 626 public String getSchemaName() 627 { 628 return schemaName; 629 } 630 631 632 /** 633 * Sets the name of the schema this SchemaObject is associated with. 634 * 635 * @param schemaName the new schema name 636 */ 637 @Override 638 public void setSchemaName( String schemaName ) 639 { 640 if ( locked ) 641 { 642 throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) ); 643 } 644 645 this.schemaName = schemaName; 646 647 computeHashCode(); 648 } 649 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 public boolean equals( Object o1 ) 656 { 657 if ( this == o1 ) 658 { 659 return true; 660 } 661 662 if ( !( o1 instanceof AbstractSchemaObject ) ) 663 { 664 return false; 665 } 666 667 AbstractSchemaObject that = ( AbstractSchemaObject ) o1; 668 669 // Two schemaObject are equals if their oid is equal, 670 // their ObjectType is equal, their names are equals 671 // their schema name is the same, all their flags are equals, 672 // the description is the same and their extensions are equals 673 if ( !compareOid( oid, that.oid ) ) 674 { 675 return false; 676 } 677 678 // Compare the names 679 if ( names == null ) 680 { 681 if ( that.names != null ) 682 { 683 return false; 684 } 685 } 686 else if ( that.names == null ) 687 { 688 return false; 689 } 690 else 691 { 692 int nbNames = 0; 693 694 for ( String name : names ) 695 { 696 if ( !that.names.contains( name ) ) 697 { 698 return false; 699 } 700 701 nbNames++; 702 } 703 704 if ( nbNames != names.size() ) 705 { 706 return false; 707 } 708 } 709 710 if ( schemaName == null ) 711 { 712 if ( that.schemaName != null ) 713 { 714 return false; 715 } 716 } 717 else 718 { 719 if ( !schemaName.equalsIgnoreCase( that.schemaName ) ) 720 { 721 return false; 722 } 723 } 724 725 if ( objectType != that.objectType ) 726 { 727 return false; 728 } 729 730 if ( extensions != null ) 731 { 732 if ( that.extensions == null ) 733 { 734 return false; 735 } 736 else 737 { 738 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 739 { 740 String key = entry.getKey(); 741 742 if ( !that.extensions.containsKey( key ) ) 743 { 744 return false; 745 } 746 747 List<String> thisValues = entry.getValue(); 748 List<String> thatValues = that.extensions.get( key ); 749 750 if ( thisValues != null ) 751 { 752 if ( thatValues == null ) 753 { 754 return false; 755 } 756 else 757 { 758 if ( thisValues.size() != thatValues.size() ) 759 { 760 return false; 761 } 762 763 // TODO compare the values 764 } 765 } 766 else if ( thatValues != null ) 767 { 768 return false; 769 } 770 } 771 } 772 } 773 else if ( that.extensions != null ) 774 { 775 return false; 776 } 777 778 if ( this.isEnabled != that.isEnabled ) 779 { 780 return false; 781 } 782 783 if ( this.isObsolete != that.isObsolete ) 784 { 785 return false; 786 } 787 788 if ( this.description == null ) 789 { 790 return that.description == null; 791 } 792 else 793 { 794 return this.description.equalsIgnoreCase( that.description ); 795 } 796 } 797 798 799 /** 800 * Compare two oids, and return true if they are both null or equal. 801 * 802 * @param oid1 the first OID 803 * @param oid2 the second OID 804 * @return <code>true</code> if both OIDs are null or equal 805 */ 806 protected boolean compareOid( String oid1, String oid2 ) 807 { 808 if ( oid1 == null ) 809 { 810 return oid2 == null; 811 } 812 else 813 { 814 return oid1.equals( oid2 ); 815 } 816 } 817 818 819 /** 820 * {@inheritDoc} 821 */ 822 @Override 823 public SchemaObject copy( SchemaObject original ) 824 { 825 // copy the description 826 description = original.getDescription(); 827 828 // copy the flags 829 isEnabled = original.isEnabled(); 830 isObsolete = original.isObsolete(); 831 832 // copy the names 833 names = new ArrayList<>(); 834 835 for ( String name : original.getNames() ) 836 { 837 names.add( name ); 838 } 839 840 // copy the extensions 841 extensions = new HashMap<>(); 842 843 for ( String key : original.getExtensions().keySet() ) 844 { 845 List<String> extensionValues = original.getExtension( key ); 846 847 List<String> cloneExtension = new ArrayList<>(); 848 849 for ( String value : extensionValues ) 850 { 851 cloneExtension.add( value ); 852 } 853 854 extensions.put( key, cloneExtension ); 855 } 856 857 // The SchemaName 858 schemaName = original.getSchemaName(); 859 860 // The specification 861 specification = original.getSpecification(); 862 863 return this; 864 } 865 866 867 /** 868 * Clear the current SchemaObject : remove all the references to other objects, 869 * and all the Maps. 870 */ 871 @Override 872 public void clear() 873 { 874 // Clear the extensions 875 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 876 { 877 List<String> extensionList = entry.getValue(); 878 879 extensionList.clear(); 880 } 881 882 extensions.clear(); 883 884 // Clear the names 885 names.clear(); 886 887 computeHashCode(); 888 } 889 890 891 /** 892 * {@inheritDoc} 893 */ 894 @Override 895 public final void lock() 896 { 897 locked = true; 898 } 899 900 901 /** 902 * Unlock the Schema Object and make it modifiable again. 903 */ 904 public void unlock() 905 { 906 locked = false; 907 } 908 909 910 /** 911 * Compute the hashcode, and store it in the 'h' variable 912 */ 913 protected void computeHashCode() 914 { 915 int hash = 37; 916 917 // The OID 918 if ( oid != null ) 919 { 920 hash += hash * 17 + oid.hashCode(); 921 } 922 923 // The SchemaObject type 924 if ( objectType != null ) 925 { 926 hash += hash * 17 + objectType.getValue(); 927 } 928 929 // The Names, if any 930 if ( ( names != null ) && !names.isEmpty() ) 931 { 932 int tempHash = 0; 933 934 for ( String name : names ) 935 { 936 tempHash *= name.hashCode(); 937 } 938 939 hash = hash * 17 + tempHash; 940 } 941 942 // The schemaName if any 943 if ( schemaName != null ) 944 { 945 hash += hash * 17 + schemaName.hashCode(); 946 } 947 948 hash += hash * 17 + ( isEnabled ? 1 : 0 ); 949 950 // The description, if any 951 if ( description != null ) 952 { 953 hash += hash * 17 + description.hashCode(); 954 } 955 956 // The extensions, if any 957 // Because the extensions and their values are stored un-ordered 958 // we have to be careful when computing the hashcode so that it does 959 // not depend on the extensions/values order 960 int tempHash = 0; 961 962 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 963 { 964 String key = entry.getKey(); 965 int tempHash2 = key.hashCode(); 966 967 List<String> values = entry.getValue(); 968 969 if ( values != null ) 970 { 971 int tempHash3 = 0; 972 973 for ( String value : values ) 974 { 975 tempHash3 += value.hashCode(); 976 } 977 978 tempHash += tempHash2 * tempHash3; 979 } 980 } 981 982 hash = hash * 17 + tempHash; 983 984 h = hash; 985 } 986 987 988 /** 989 * {@inheritDoc} 990 */ 991 @Override 992 public int hashCode() 993 { 994 return h; 995 } 996}