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 * @return The SchemaObject extensions, as a Map of [extension, values] 481 */ 482 public Map<String, List<String>> getExtensions() 483 { 484 return extensions; 485 } 486 487 488 /** 489 * Add an extension with its values 490 * @param key The extension key 491 * @param values The associated values 492 */ 493 public void addExtension( String key, String... values ) 494 { 495 if ( locked ) 496 { 497 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 498 } 499 500 if ( !isReadOnly ) 501 { 502 List<String> valueList = new ArrayList<String>(); 503 504 for ( String value : values ) 505 { 506 valueList.add( value ); 507 } 508 509 extensions.put( key, valueList ); 510 } 511 } 512 513 514 /** 515 * Add an extension with its values 516 * @param key The extension key 517 * @param values The associated values 518 */ 519 public void addExtension( String key, List<String> values ) 520 { 521 if ( locked ) 522 { 523 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 524 } 525 526 if ( !isReadOnly ) 527 { 528 extensions.put( key, values ); 529 } 530 } 531 532 533 /** 534 * Add an extensions with their values. (Actually do a copy) 535 * 536 * @param extensions The extensions map 537 */ 538 public void setExtensions( Map<String, List<String>> extensions ) 539 { 540 if ( locked ) 541 { 542 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 543 } 544 545 if ( !isReadOnly && ( extensions != null ) ) 546 { 547 this.extensions = new HashMap<String, List<String>>(); 548 549 for ( Map.Entry<String, List<String>> entry : extensions.entrySet() ) 550 { 551 List<String> values = new ArrayList<String>(); 552 553 for ( String value : entry.getValue() ) 554 { 555 values.add( value ); 556 } 557 558 this.extensions.put( entry.getKey(), values ); 559 } 560 561 } 562 } 563 564 565 /** 566 * The SchemaObject type : 567 * <ul> 568 * <li> AttributeType 569 * <li> DitCOntentRule 570 * <li> DitStructureRule 571 * <li> LdapComparator (specific to ADS) 572 * <li> LdapSyntaxe 573 * <li> MatchingRule 574 * <li> MatchingRuleUse 575 * <li> NameForm 576 * <li> Normalizer (specific to ADS) 577 * <li> ObjectClass 578 * <li> SyntaxChecker (specific to ADS) 579 * </ul> 580 * 581 * @return the SchemaObject type 582 */ 583 public SchemaObjectType getObjectType() 584 { 585 return objectType; 586 } 587 588 589 /** 590 * Gets the name of the schema this SchemaObject is associated with. 591 * 592 * @return the name of the schema associated with this schemaObject 593 */ 594 public String getSchemaName() 595 { 596 return schemaName; 597 } 598 599 600 /** 601 * Sets the name of the schema this SchemaObject is associated with. 602 * 603 * @param schemaName the new schema name 604 */ 605 public void setSchemaName( String schemaName ) 606 { 607 if ( locked ) 608 { 609 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) ); 610 } 611 612 if ( !isReadOnly ) 613 { 614 this.schemaName = schemaName; 615 } 616 } 617 618 619 /** 620 * This method is final to forbid the inherited classes to implement 621 * it. This has been done for performances reasons : the hashcode should 622 * be computed only once, and stored locally. 623 * 624 * The hashcode is currently computed in the lock() method, which is a hack 625 * that should be fixed. 626 * 627 * @return {@inheritDoc} 628 */ 629 @Override 630 public final int hashCode() 631 { 632 return h; 633 } 634 635 636 /** 637 * @{@inheritDoc} 638 */ 639 @Override 640 public boolean equals( Object o1 ) 641 { 642 if ( this == o1 ) 643 { 644 return true; 645 } 646 647 if ( !( o1 instanceof AbstractSchemaObject ) ) 648 { 649 return false; 650 } 651 652 AbstractSchemaObject that = ( AbstractSchemaObject ) o1; 653 654 // Two schemaObject are equals if their oid is equal, 655 // their ObjectType is equal, their names are equals 656 // their schema name is the same, all their flags are equals, 657 // the description is the same and their extensions are equals 658 if ( !compareOid( oid, that.oid ) ) 659 { 660 return false; 661 } 662 663 // Compare the names 664 if ( names == null ) 665 { 666 if ( that.names != null ) 667 { 668 return false; 669 } 670 } 671 else if ( that.names == null ) 672 { 673 return false; 674 } 675 else 676 { 677 int nbNames = 0; 678 679 for ( String name : names ) 680 { 681 if ( !that.names.contains( name ) ) 682 { 683 return false; 684 } 685 686 nbNames++; 687 } 688 689 if ( nbNames != names.size() ) 690 { 691 return false; 692 } 693 } 694 695 if ( schemaName == null ) 696 { 697 if ( that.schemaName != null ) 698 { 699 return false; 700 } 701 } 702 else 703 { 704 if ( !schemaName.equalsIgnoreCase( that.schemaName ) ) 705 { 706 return false; 707 } 708 } 709 710 if ( objectType != that.objectType ) 711 { 712 return false; 713 } 714 715 if ( extensions != null ) 716 { 717 if ( that.extensions == null ) 718 { 719 return false; 720 } 721 else 722 { 723 for ( String key : extensions.keySet() ) 724 { 725 if ( !that.extensions.containsKey( key ) ) 726 { 727 return false; 728 } 729 730 List<String> thisValues = extensions.get( key ); 731 List<String> thatValues = that.extensions.get( key ); 732 733 if ( thisValues != null ) 734 { 735 if ( thatValues == null ) 736 { 737 return false; 738 } 739 else 740 { 741 if ( thisValues.size() != thatValues.size() ) 742 { 743 return false; 744 } 745 746 // TODO compare the values 747 } 748 } 749 else if ( thatValues != null ) 750 { 751 return false; 752 } 753 } 754 } 755 } 756 else if ( that.extensions != null ) 757 { 758 return false; 759 } 760 761 if ( this.isEnabled != that.isEnabled ) 762 { 763 return false; 764 } 765 766 if ( this.isObsolete != that.isObsolete ) 767 { 768 return false; 769 } 770 771 if ( this.isReadOnly != that.isReadOnly ) 772 { 773 return false; 774 } 775 776 if ( this.description == null ) 777 { 778 return that.description == null; 779 } 780 else 781 { 782 return this.description.equalsIgnoreCase( that.description ); 783 } 784 } 785 786 787 /** 788 * Copy the current SchemaObject on place 789 * 790 * @return The copied SchemaObject 791 */ 792 public abstract SchemaObject copy(); 793 794 795 /** 796 * Compare two oids, and return true if they are both null or equal. 797 * 798 * @param oid1 the first OID 799 * @param oid2 the second OID 800 * @return <code>true</code> if both OIDs are null or equal 801 */ 802 protected boolean compareOid( String oid1, String oid2 ) 803 { 804 if ( oid1 == null ) 805 { 806 return oid2 == null; 807 } 808 else 809 { 810 return oid1.equals( oid2 ); 811 } 812 } 813 814 815 /** 816 * {@inheritDoc} 817 */ 818 public SchemaObject copy( SchemaObject original ) 819 { 820 // copy the description 821 description = original.getDescription(); 822 823 // copy the flags 824 isEnabled = original.isEnabled(); 825 isObsolete = original.isObsolete(); 826 isReadOnly = original.isReadOnly(); 827 828 // copy the names 829 names = new ArrayList<String>(); 830 831 for ( String name : original.getNames() ) 832 { 833 names.add( name ); 834 } 835 836 // copy the extensions 837 extensions = new HashMap<String, List<String>>(); 838 839 for ( String key : original.getExtensions().keySet() ) 840 { 841 List<String> extensionValues = original.getExtensions().get( key ); 842 843 List<String> cloneExtension = new ArrayList<String>(); 844 845 for ( String value : extensionValues ) 846 { 847 cloneExtension.add( value ); 848 } 849 850 extensions.put( key, cloneExtension ); 851 } 852 853 // The SchemaName 854 schemaName = original.getSchemaName(); 855 856 // The specification 857 specification = original.getSpecification(); 858 859 return this; 860 } 861 862 863 /** 864 * Clear the current SchemaObject : remove all the references to other objects, 865 * and all the Maps. 866 */ 867 public void clear() 868 { 869 // Clear the extensions 870 for ( String extension : extensions.keySet() ) 871 { 872 List<String> extensionList = extensions.get( extension ); 873 874 extensionList.clear(); 875 } 876 877 extensions.clear(); 878 879 // Clear the names 880 names.clear(); 881 } 882 883 884 public void unlock() 885 { 886 locked = false; 887 } 888 889 890 /** 891 * {@inheritDoc} 892 */ 893 public final void lock() 894 { 895 if ( locked ) 896 { 897 return; 898 } 899 900 h = 37; 901 902 // The OID 903 h += h * 17 + oid.hashCode(); 904 905 // The SchemaObject type 906 h += h * 17 + objectType.getValue(); 907 908 // The Names, if any 909 if ( ( names != null ) && ( names.size() != 0 ) ) 910 { 911 for ( String name : names ) 912 { 913 h += h * 17 + name.hashCode(); 914 } 915 } 916 917 // The schemaName if any 918 if ( schemaName != null ) 919 { 920 h += h * 17 + schemaName.hashCode(); 921 } 922 923 h += h * 17 + ( isEnabled ? 1 : 0 ); 924 h += h * 17 + ( isReadOnly ? 1 : 0 ); 925 926 // The description, if any 927 if ( description != null ) 928 { 929 h += h * 17 + description.hashCode(); 930 } 931 932 // The extensions, if any 933 for ( String key : extensions.keySet() ) 934 { 935 h += h * 17 + key.hashCode(); 936 937 List<String> values = extensions.get( key ); 938 939 if ( values != null ) 940 { 941 for ( String value : values ) 942 { 943 h += h * 17 + value.hashCode(); 944 } 945 } 946 } 947 948 locked = true; 949 } 950}