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