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