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 * http://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 List<String> names; 088 089 /** Whether or not this SchemaObject is enabled */ 090 protected boolean isEnabled = true; 091 092 /** Whether or not this SchemaObject can be modified */ 093 protected boolean isReadOnly = false; 094 095 /** Whether or not this SchemaObject is obsolete */ 096 protected boolean isObsolete = false; 097 098 /** A short description of this SchemaObject */ 099 protected String description; 100 101 /** The SchemaObject specification */ 102 protected String specification; 103 104 /** The name of the schema this object is associated with */ 105 protected String schemaName; 106 107 /** The SchemaObjectType */ 108 protected SchemaObjectType objectType; 109 110 /** A map containing the list of supported extensions */ 111 protected Map<String, List<String>> extensions; 112 113 /** A locked to avoid modifications when set to true */ 114 protected volatile boolean locked; 115 116 /** The hashcode for this schemaObject */ 117 private int h; 118 119 120 /** 121 * A constructor for a SchemaObject instance. It must be 122 * invoked by the inherited class. 123 * 124 * @param objectType The SchemaObjectType to create 125 * @param oid the SchemaObject numeric OID 126 */ 127 protected AbstractSchemaObject( SchemaObjectType objectType, String oid ) 128 { 129 this.objectType = objectType; 130 this.oid = oid; 131 extensions = new HashMap<>(); 132 names = new ArrayList<>(); 133 } 134 135 136 /** 137 * Constructor used when a generic reusable SchemaObject is assigned an 138 * OID after being instantiated. 139 * 140 * @param objectType The SchemaObjectType to create 141 */ 142 protected AbstractSchemaObject( SchemaObjectType objectType ) 143 { 144 this.objectType = objectType; 145 extensions = new HashMap<>(); 146 names = new ArrayList<>(); 147 } 148 149 150 /** 151 * Gets usually what is the numeric object identifier assigned to this 152 * SchemaObject. All schema objects except for MatchingRuleUses have an OID 153 * assigned specifically to then. A MatchingRuleUse's OID really is the OID 154 * of it's MatchingRule and not specific to the MatchingRuleUse. This 155 * effects how MatchingRuleUse objects are maintained by the system. 156 * 157 * @return an OID for this SchemaObject or its MatchingRule if this 158 * SchemaObject is a MatchingRuleUse object 159 */ 160 @Override 161 public String getOid() 162 { 163 return oid; 164 } 165 166 167 /** 168 * A special method used when renaming an SchemaObject: we may have to 169 * change it's OID 170 * @param oid The new OID 171 */ 172 @Override 173 public void setOid( String oid ) 174 { 175 if ( locked ) 176 { 177 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 178 } 179 180 this.oid = oid; 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 lowercased 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_04441, getName() ) ); 237 } 238 239 if ( !isReadOnly ) 240 { 241 // We must avoid duplicated names, as names are case insensitive 242 Set<String> lowerNames = new HashSet<>(); 243 244 // Fills a set with all the existing names 245 for ( String name : this.names ) 246 { 247 lowerNames.add( Strings.toLowerCaseAscii( name ) ); 248 } 249 250 for ( String name : namesToAdd ) 251 { 252 if ( name != null ) 253 { 254 String lowerName = Strings.toLowerCaseAscii( name ); 255 // Check that the lower cased names is not already present 256 if ( !lowerNames.contains( lowerName ) ) 257 { 258 this.names.add( name ); 259 lowerNames.add( lowerName ); 260 } 261 } 262 } 263 } 264 } 265 266 267 /** 268 * Sets the list of names for this SchemaObject. The names are 269 * lowercased and trimmed. 270 * 271 * @param names The list of names. Can be empty 272 */ 273 @Override 274 public void setNames( List<String> names ) 275 { 276 if ( locked ) 277 { 278 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 279 } 280 281 if ( names == null ) 282 { 283 return; 284 } 285 286 if ( !isReadOnly ) 287 { 288 this.names = new ArrayList<>( names.size() ); 289 290 for ( String name : names ) 291 { 292 if ( name != null ) 293 { 294 this.names.add( name ); 295 } 296 } 297 } 298 } 299 300 301 /** 302 * Sets the list of names for this SchemaObject. The names are 303 * lowercased and trimmed. 304 * 305 * @param names The list of names. 306 */ 307 public void setNames( String... names ) 308 { 309 if ( locked ) 310 { 311 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 312 } 313 314 if ( names == null ) 315 { 316 return; 317 } 318 319 if ( !isReadOnly ) 320 { 321 this.names.clear(); 322 323 for ( String name : names ) 324 { 325 if ( name != null ) 326 { 327 this.names.add( name ); 328 } 329 } 330 } 331 } 332 333 334 /** 335 * Gets a short description about this SchemaObject. 336 * 337 * @return a short description about this SchemaObject 338 */ 339 @Override 340 public String getDescription() 341 { 342 return description; 343 } 344 345 346 /** 347 * Sets the SchemaObject's description 348 * 349 * @param description The SchemaObject's description 350 */ 351 @Override 352 public void setDescription( String description ) 353 { 354 if ( locked ) 355 { 356 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 357 } 358 359 if ( !isReadOnly ) 360 { 361 this.description = description; 362 } 363 } 364 365 366 /** 367 * Gets the SchemaObject specification. 368 * 369 * @return the SchemaObject specification 370 */ 371 @Override 372 public String getSpecification() 373 { 374 return specification; 375 } 376 377 378 /** 379 * Sets the SchemaObject's specification 380 * 381 * @param specification The SchemaObject's specification 382 */ 383 @Override 384 public void setSpecification( String specification ) 385 { 386 if ( locked ) 387 { 388 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 389 } 390 391 if ( !isReadOnly ) 392 { 393 this.specification = specification; 394 } 395 } 396 397 398 /** 399 * Tells if this SchemaObject is enabled. 400 * 401 * @return true if the SchemaObject is enabled, or if it depends on 402 * an enabled schema 403 */ 404 @Override 405 public boolean isEnabled() 406 { 407 return isEnabled; 408 } 409 410 411 /** 412 * Tells if this SchemaObject is disabled. 413 * 414 * @return true if the SchemaObject is disabled 415 */ 416 @Override 417 public boolean isDisabled() 418 { 419 return !isEnabled; 420 } 421 422 423 /** 424 * Sets the SchemaObject state, either enabled or disabled. 425 * 426 * @param enabled The current SchemaObject state 427 */ 428 @Override 429 public void setEnabled( boolean enabled ) 430 { 431 if ( !isReadOnly ) 432 { 433 isEnabled = enabled; 434 } 435 } 436 437 438 /** 439 * Tells if this SchemaObject is ReadOnly. 440 * 441 * @return true if the SchemaObject is not modifiable 442 */ 443 @Override 444 public boolean isReadOnly() 445 { 446 return isReadOnly; 447 } 448 449 450 /** 451 * Sets the SchemaObject readOnly flag 452 * 453 * @param readOnly The current SchemaObject ReadOnly status 454 */ 455 @Override 456 public void setReadOnly( boolean readOnly ) 457 { 458 if ( locked ) 459 { 460 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 461 } 462 463 this.isReadOnly = readOnly; 464 } 465 466 467 /** 468 * Gets whether or not this SchemaObject has been inactivated. All 469 * SchemaObjects except Syntaxes allow for this parameter within their 470 * definition. For Syntaxes this property should always return false in 471 * which case it is never included in the description. 472 * 473 * @return true if inactive, false if active 474 */ 475 @Override 476 public boolean isObsolete() 477 { 478 return isObsolete; 479 } 480 481 482 /** 483 * Sets the Obsolete flag. 484 * 485 * @param obsolete The Obsolete flag state 486 */ 487 @Override 488 public void setObsolete( boolean obsolete ) 489 { 490 if ( locked ) 491 { 492 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 493 } 494 495 if ( !isReadOnly ) 496 { 497 this.isObsolete = obsolete; 498 } 499 } 500 501 502 /** 503 * {@inheritDoc} 504 */ 505 @Override 506 public Map<String, List<String>> getExtensions() 507 { 508 return extensions; 509 } 510 511 512 /** 513 * {@inheritDoc} 514 */ 515 @Override 516public boolean hasExtension( String extension ) 517 { 518 return extensions.containsKey( Strings.toUpperCaseAscii( extension ) ); 519 } 520 521 522 /** 523 * {@inheritDoc} 524 */ 525 @Override 526 public List<String> getExtension( String extension ) 527 { 528 String name = Strings.toUpperCaseAscii( extension ); 529 530 if ( hasExtension( name ) ) 531 { 532 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 533 { 534 String key = entry.getKey(); 535 536 if ( name.equalsIgnoreCase( key ) ) 537 { 538 return entry.getValue(); 539 } 540 } 541 } 542 543 return null; 544 } 545 546 547 /** 548 * Add an extension with its values 549 * @param key The extension key 550 * @param values The associated values 551 */ 552 @Override 553 public void addExtension( String key, String... values ) 554 { 555 if ( locked ) 556 { 557 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 558 } 559 560 if ( !isReadOnly ) 561 { 562 List<String> valueList = new ArrayList<>(); 563 564 for ( String value : values ) 565 { 566 valueList.add( value ); 567 } 568 569 extensions.put( Strings.toUpperCaseAscii( key ), valueList ); 570 } 571 } 572 573 574 /** 575 * Add an extension with its values 576 * @param key The extension key 577 * @param values The associated values 578 */ 579 @Override 580 public void addExtension( String key, List<String> values ) 581 { 582 if ( locked ) 583 { 584 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 585 } 586 587 if ( !isReadOnly ) 588 { 589 extensions.put( Strings.toUpperCaseAscii( key ), values ); 590 } 591 } 592 593 594 /** 595 * Add an extensions with their values. (Actually do a copy) 596 * 597 * @param extensions The extensions map 598 */ 599 @Override 600 public void setExtensions( Map<String, List<String>> extensions ) 601 { 602 if ( locked ) 603 { 604 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 605 } 606 607 if ( !isReadOnly && ( extensions != null ) ) 608 { 609 this.extensions = new HashMap<>(); 610 611 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 612 { 613 List<String> values = new ArrayList<>(); 614 615 for ( String value : entry.getValue() ) 616 { 617 values.add( value ); 618 } 619 620 this.extensions.put( Strings.toUpperCaseAscii( entry.getKey() ), values ); 621 } 622 623 } 624 } 625 626 627 /** 628 * The SchemaObject type : 629 * <ul> 630 * <li> AttributeType 631 * <li> DitCOntentRule 632 * <li> DitStructureRule 633 * <li> LdapComparator (specific to ADS) 634 * <li> LdapSyntaxe 635 * <li> MatchingRule 636 * <li> MatchingRuleUse 637 * <li> NameForm 638 * <li> Normalizer (specific to ADS) 639 * <li> ObjectClass 640 * <li> SyntaxChecker (specific to ADS) 641 * </ul> 642 * 643 * @return the SchemaObject type 644 */ 645 @Override 646 public SchemaObjectType getObjectType() 647 { 648 return objectType; 649 } 650 651 652 /** 653 * Gets the name of the schema this SchemaObject is associated with. 654 * 655 * @return the name of the schema associated with this schemaObject 656 */ 657 @Override 658 public String getSchemaName() 659 { 660 return schemaName; 661 } 662 663 664 /** 665 * Sets the name of the schema this SchemaObject is associated with. 666 * 667 * @param schemaName the new schema name 668 */ 669 @Override 670 public void setSchemaName( String schemaName ) 671 { 672 if ( locked ) 673 { 674 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 675 } 676 677 if ( !isReadOnly ) 678 { 679 this.schemaName = schemaName; 680 } 681 } 682 683 684 /** 685 * This method is final to forbid the inherited classes to implement 686 * it. This has been done for performances reasons : the hashcode should 687 * be computed only once, and stored locally. 688 * 689 * The hashcode is currently computed in the lock() method, which is a hack 690 * that should be fixed. 691 * 692 * @return {@inheritDoc} 693 */ 694 @Override 695 public final int hashCode() 696 { 697 return h; 698 } 699 700 701 /** 702 * {@inheritDoc} 703 */ 704 @Override 705 public boolean equals( Object o1 ) 706 { 707 if ( this == o1 ) 708 { 709 return true; 710 } 711 712 if ( !( o1 instanceof AbstractSchemaObject ) ) 713 { 714 return false; 715 } 716 717 AbstractSchemaObject that = ( AbstractSchemaObject ) o1; 718 719 // Two schemaObject are equals if their oid is equal, 720 // their ObjectType is equal, their names are equals 721 // their schema name is the same, all their flags are equals, 722 // the description is the same and their extensions are equals 723 if ( !compareOid( oid, that.oid ) ) 724 { 725 return false; 726 } 727 728 // Compare the names 729 if ( names == null ) 730 { 731 if ( that.names != null ) 732 { 733 return false; 734 } 735 } 736 else if ( that.names == null ) 737 { 738 return false; 739 } 740 else 741 { 742 int nbNames = 0; 743 744 for ( String name : names ) 745 { 746 if ( !that.names.contains( name ) ) 747 { 748 return false; 749 } 750 751 nbNames++; 752 } 753 754 if ( nbNames != names.size() ) 755 { 756 return false; 757 } 758 } 759 760 if ( schemaName == null ) 761 { 762 if ( that.schemaName != null ) 763 { 764 return false; 765 } 766 } 767 else 768 { 769 if ( !schemaName.equalsIgnoreCase( that.schemaName ) ) 770 { 771 return false; 772 } 773 } 774 775 if ( objectType != that.objectType ) 776 { 777 return false; 778 } 779 780 if ( extensions != null ) 781 { 782 if ( that.extensions == null ) 783 { 784 return false; 785 } 786 else 787 { 788 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 789 { 790 String key = entry.getKey(); 791 792 if ( !that.extensions.containsKey( key ) ) 793 { 794 return false; 795 } 796 797 List<String> thisValues = entry.getValue(); 798 List<String> thatValues = that.extensions.get( key ); 799 800 if ( thisValues != null ) 801 { 802 if ( thatValues == null ) 803 { 804 return false; 805 } 806 else 807 { 808 if ( thisValues.size() != thatValues.size() ) 809 { 810 return false; 811 } 812 813 // TODO compare the values 814 } 815 } 816 else if ( thatValues != null ) 817 { 818 return false; 819 } 820 } 821 } 822 } 823 else if ( that.extensions != null ) 824 { 825 return false; 826 } 827 828 if ( this.isEnabled != that.isEnabled ) 829 { 830 return false; 831 } 832 833 if ( this.isObsolete != that.isObsolete ) 834 { 835 return false; 836 } 837 838 if ( this.isReadOnly != that.isReadOnly ) 839 { 840 return false; 841 } 842 843 if ( this.description == null ) 844 { 845 return that.description == null; 846 } 847 else 848 { 849 return this.description.equalsIgnoreCase( that.description ); 850 } 851 } 852 853 854 /** 855 * Compare two oids, and return true if they are both null or equal. 856 * 857 * @param oid1 the first OID 858 * @param oid2 the second OID 859 * @return <code>true</code> if both OIDs are null or equal 860 */ 861 protected boolean compareOid( String oid1, String oid2 ) 862 { 863 if ( oid1 == null ) 864 { 865 return oid2 == null; 866 } 867 else 868 { 869 return oid1.equals( oid2 ); 870 } 871 } 872 873 874 /** 875 * {@inheritDoc} 876 */ 877 @Override 878 public SchemaObject copy( SchemaObject original ) 879 { 880 // copy the description 881 description = original.getDescription(); 882 883 // copy the flags 884 isEnabled = original.isEnabled(); 885 isObsolete = original.isObsolete(); 886 isReadOnly = original.isReadOnly(); 887 888 // copy the names 889 names = new ArrayList<>(); 890 891 for ( String name : original.getNames() ) 892 { 893 names.add( name ); 894 } 895 896 // copy the extensions 897 extensions = new HashMap<>(); 898 899 for ( String key : original.getExtensions().keySet() ) 900 { 901 List<String> extensionValues = original.getExtension( key ); 902 903 List<String> cloneExtension = new ArrayList<>(); 904 905 for ( String value : extensionValues ) 906 { 907 cloneExtension.add( value ); 908 } 909 910 extensions.put( key, cloneExtension ); 911 } 912 913 // The SchemaName 914 schemaName = original.getSchemaName(); 915 916 // The specification 917 specification = original.getSpecification(); 918 919 return this; 920 } 921 922 923 /** 924 * Clear the current SchemaObject : remove all the references to other objects, 925 * and all the Maps. 926 */ 927 @Override 928 public void clear() 929 { 930 // Clear the extensions 931 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 932 { 933 List<String> extensionList = entry.getValue(); 934 935 extensionList.clear(); 936 } 937 938 extensions.clear(); 939 940 // Clear the names 941 names.clear(); 942 } 943 944 945 /** 946 * Unlock the Schema Object and make it modifiable again. 947 */ 948 public void unlock() 949 { 950 locked = false; 951 } 952 953 954 /** 955 * {@inheritDoc} 956 */ 957 @Override 958 public final void lock() 959 { 960 if ( locked ) 961 { 962 return; 963 } 964 965 h = 37; 966 967 // The OID 968 h += h * 17 + oid.hashCode(); 969 970 // The SchemaObject type 971 h += h * 17 + objectType.getValue(); 972 973 // The Names, if any 974 if ( ( names != null ) && !names.isEmpty() ) 975 { 976 for ( String name : names ) 977 { 978 h += h * 17 + name.hashCode(); 979 } 980 } 981 982 // The schemaName if any 983 if ( schemaName != null ) 984 { 985 h += h * 17 + schemaName.hashCode(); 986 } 987 988 h += h * 17 + ( isEnabled ? 1 : 0 ); 989 h += h * 17 + ( isReadOnly ? 1 : 0 ); 990 991 // The description, if any 992 if ( description != null ) 993 { 994 h += h * 17 + description.hashCode(); 995 } 996 997 // The extensions, if any 998 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 999 { 1000 String key = entry.getKey(); 1001 h += h * 17 + key.hashCode(); 1002 1003 List<String> values = entry.getValue(); 1004 1005 if ( values != null ) 1006 { 1007 for ( String value : values ) 1008 { 1009 h += h * 17 + value.hashCode(); 1010 } 1011 } 1012 } 1013 1014 locked = true; 1015 } 1016}