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 */ 020 021 package org.apache.directory.shared.ldap.ldif; 022 023 import java.io.Externalizable; 024 import java.io.IOException; 025 import java.io.ObjectInput; 026 import java.io.ObjectOutput; 027 import java.util.HashMap; 028 import java.util.LinkedList; 029 import java.util.List; 030 import java.util.Map; 031 032 import org.apache.directory.shared.ldap.entry.StringValue; 033 import org.apache.directory.shared.ldap.entry.Entry; 034 import org.apache.directory.shared.ldap.entry.EntryAttribute; 035 import org.apache.directory.shared.ldap.entry.Modification; 036 import org.apache.directory.shared.ldap.entry.ModificationOperation; 037 import org.apache.directory.shared.ldap.entry.Value; 038 import org.apache.directory.shared.ldap.entry.client.ClientModification; 039 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute; 040 import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry; 041 import org.apache.directory.shared.ldap.exception.LdapException; 042 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException; 043 import org.apache.directory.shared.ldap.message.control.Control; 044 import org.apache.directory.shared.ldap.name.DN; 045 import org.apache.directory.shared.ldap.name.RDN; 046 import org.apache.directory.shared.ldap.util.StringTools; 047 048 049 /** 050 * A entry to be populated by an ldif parser. 051 * 052 * We will have different kind of entries : 053 * - added entries 054 * - deleted entries 055 * - modified entries 056 * - RDN modified entries 057 * - DN modified entries 058 * 059 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 060 * @version $Rev$, $Date$ 061 */ 062 public class LdifEntry implements Cloneable, Externalizable 063 { 064 private static final long serialVersionUID = 2L; 065 066 /** Used in toArray() */ 067 public static final Modification[] EMPTY_MODS = new Modification[0]; 068 069 /** the change type */ 070 private ChangeType changeType; 071 072 /** the modification item list */ 073 private List<Modification> modificationList; 074 075 private Map<String, Modification> modificationItems; 076 077 /** The new superior */ 078 private String newSuperior; 079 080 /** The new rdn */ 081 private String newRdn; 082 083 /** The delete old rdn flag */ 084 private boolean deleteOldRdn; 085 086 /** the entry */ 087 private Entry entry; 088 089 090 /** The control */ 091 private Control control; 092 093 /** 094 * Creates a new Entry object. 095 */ 096 public LdifEntry() 097 { 098 changeType = ChangeType.Add; // Default LDIF content 099 modificationList = new LinkedList<Modification>(); 100 modificationItems = new HashMap<String, Modification>(); 101 entry = new DefaultClientEntry( null ); 102 control = null; 103 } 104 105 106 /** 107 * Set the Distinguished Name 108 * 109 * @param dn 110 * The Distinguished Name 111 */ 112 public void setDn( DN dn ) 113 { 114 entry.setDn( (DN)dn.clone() ); 115 } 116 117 118 /** 119 * Set the Distinguished Name 120 * 121 * @param dn The Distinguished Name 122 */ 123 public void setDn( String dn ) throws LdapInvalidDnException 124 { 125 entry.setDn( new DN( dn ) ); 126 } 127 128 129 /** 130 * Set the modification type 131 * 132 * @param changeType 133 * The change type 134 * 135 */ 136 public void setChangeType( ChangeType changeType ) 137 { 138 this.changeType = changeType; 139 } 140 141 /** 142 * Set the change type 143 * 144 * @param changeType 145 * The change type 146 */ 147 public void setChangeType( String changeType ) 148 { 149 if ( "add".equals( changeType ) ) 150 { 151 this.changeType = ChangeType.Add; 152 } 153 else if ( "modify".equals( changeType ) ) 154 { 155 this.changeType = ChangeType.Modify; 156 } 157 else if ( "moddn".equals( changeType ) ) 158 { 159 this.changeType = ChangeType.ModDn; 160 } 161 else if ( "modrdn".equals( changeType ) ) 162 { 163 this.changeType = ChangeType.ModRdn; 164 } 165 else if ( "delete".equals( changeType ) ) 166 { 167 this.changeType = ChangeType.Delete; 168 } 169 } 170 171 /** 172 * Add a modification item (used by modify operations) 173 * 174 * @param modification The modification to be added 175 */ 176 public void addModificationItem( Modification modification ) 177 { 178 if ( changeType == ChangeType.Modify ) 179 { 180 modificationList.add( modification ); 181 modificationItems.put( modification.getAttribute().getId(), modification ); 182 } 183 } 184 185 /** 186 * Add a modification item (used by modify operations) 187 * 188 * @param modOp The operation. One of : 189 * - ModificationOperation.ADD_ATTRIBUTE 190 * - ModificationOperation.REMOVE_ATTRIBUTE 191 * - ModificationOperation.REPLACE_ATTRIBUTE 192 * 193 * @param attr The attribute to be added 194 */ 195 public void addModificationItem( ModificationOperation modOp, EntryAttribute attr ) 196 { 197 if ( changeType == ChangeType.Modify ) 198 { 199 Modification item = new ClientModification( modOp, attr ); 200 modificationList.add( item ); 201 modificationItems.put( attr.getId(), item ); 202 } 203 } 204 205 206 /** 207 * Add a modification item 208 * 209 * @param modOp The operation. One of : 210 * - ModificationOperation.ADD_ATTRIBUTE 211 * - ModificationOperation.REMOVE_ATTRIBUTE 212 * - ModificationOperation.REPLACE_ATTRIBUTE 213 * 214 * @param modOp The modification operation value 215 * @param id The attribute's ID 216 * @param value The attribute's value 217 */ 218 public void addModificationItem( ModificationOperation modOp, String id, Object value ) 219 { 220 if ( changeType == ChangeType.Modify ) 221 { 222 EntryAttribute attr = null; 223 224 if ( value == null ) 225 { 226 value = new StringValue( (String)null ); 227 attr = new DefaultClientAttribute( id, (Value<?>)value ); 228 } 229 else 230 { 231 attr = (EntryAttribute)value; 232 } 233 234 Modification item = new ClientModification( modOp, attr ); 235 modificationList.add( item ); 236 modificationItems.put( id, item ); 237 } 238 } 239 240 241 /** 242 * Add an attribute to the entry 243 * 244 * @param attr 245 * The attribute to be added 246 */ 247 public void addAttribute( EntryAttribute attr ) throws LdapException 248 { 249 entry.put( attr ); 250 } 251 252 /** 253 * Add an attribute to the entry 254 * 255 * @param id 256 * The attribute ID 257 * 258 * @param value 259 * The attribute value 260 * 261 */ 262 public void addAttribute( String id, Object value ) throws LdapException 263 { 264 if ( value instanceof String ) 265 { 266 entry.add( id, (String)value ); 267 } 268 else 269 { 270 entry.add( id, (byte[])value ); 271 } 272 } 273 274 275 /** 276 * Remove a list of Attributes from the LdifEntry 277 * 278 * @param ids The Attributes to remove 279 * @return The list of removed EntryAttributes 280 */ 281 public List<EntryAttribute> removeAttribute( String... ids ) 282 { 283 if ( entry.containsAttribute( ids ) ) 284 { 285 return entry.removeAttributes( ids ); 286 } 287 else 288 { 289 return null; 290 } 291 } 292 293 /** 294 * Add an attribute value to an existing attribute 295 * 296 * @param id 297 * The attribute ID 298 * 299 * @param value 300 * The attribute value 301 * 302 */ 303 public void putAttribute( String id, Object value ) throws LdapException 304 { 305 if ( value instanceof String ) 306 { 307 entry.add( id, (String)value ); 308 } 309 else 310 { 311 entry.add( id, (byte[])value ); 312 } 313 } 314 315 /** 316 * Get the change type 317 * 318 * @return The change type. One of : ADD = 0; MODIFY = 1; MODDN = 2; MODRDN = 319 * 3; DELETE = 4; 320 */ 321 public ChangeType getChangeType() 322 { 323 return changeType; 324 } 325 326 /** 327 * @return The list of modification items 328 */ 329 public List<Modification> getModificationItems() 330 { 331 return modificationList; 332 } 333 334 335 /** 336 * Gets the modification items as an array. 337 * 338 * @return modification items as an array. 339 */ 340 public Modification[] getModificationItemsArray() 341 { 342 return modificationList.toArray( EMPTY_MODS ); 343 } 344 345 346 /** 347 * @return The entry Distinguished name 348 */ 349 public DN getDn() 350 { 351 return entry.getDn(); 352 } 353 354 /** 355 * @return The number of entry modifications 356 */ 357 public int size() 358 { 359 return modificationList.size(); 360 } 361 362 /** 363 * Returns a attribute given it's id 364 * 365 * @param attributeId 366 * The attribute Id 367 * @return The attribute if it exists 368 */ 369 public EntryAttribute get( String attributeId ) 370 { 371 if ( "dn".equalsIgnoreCase( attributeId ) ) 372 { 373 return new DefaultClientAttribute( "dn", entry.getDn().getName() ); 374 } 375 376 return entry.get( attributeId ); 377 } 378 379 /** 380 * Get the entry's entry 381 * 382 * @return the stored Entry 383 */ 384 public Entry getEntry() 385 { 386 if ( isEntry() ) 387 { 388 return entry; 389 } 390 else 391 { 392 return null; 393 } 394 } 395 396 /** 397 * @return True, if the old RDN should be deleted. 398 */ 399 public boolean isDeleteOldRdn() 400 { 401 return deleteOldRdn; 402 } 403 404 /** 405 * Set the flage deleteOldRdn 406 * 407 * @param deleteOldRdn 408 * True if the old RDN should be deleted 409 */ 410 public void setDeleteOldRdn( boolean deleteOldRdn ) 411 { 412 this.deleteOldRdn = deleteOldRdn; 413 } 414 415 /** 416 * @return The new RDN 417 */ 418 public String getNewRdn() 419 { 420 return newRdn; 421 } 422 423 /** 424 * Set the new RDN 425 * 426 * @param newRdn 427 * The new RDN 428 */ 429 public void setNewRdn( String newRdn ) 430 { 431 this.newRdn = newRdn; 432 } 433 434 /** 435 * @return The new superior 436 */ 437 public String getNewSuperior() 438 { 439 return newSuperior; 440 } 441 442 /** 443 * Set the new superior 444 * 445 * @param newSuperior 446 * The new Superior 447 */ 448 public void setNewSuperior( String newSuperior ) 449 { 450 this.newSuperior = newSuperior; 451 } 452 453 /** 454 * @return True if the entry is an ADD entry 455 */ 456 public boolean isChangeAdd() 457 { 458 return changeType == ChangeType.Add; 459 } 460 461 /** 462 * @return True if the entry is a DELETE entry 463 */ 464 public boolean isChangeDelete() 465 { 466 return changeType == ChangeType.Delete; 467 } 468 469 /** 470 * @return True if the entry is a MODDN entry 471 */ 472 public boolean isChangeModDn() 473 { 474 return changeType == ChangeType.ModDn; 475 } 476 477 /** 478 * @return True if the entry is a MODRDN entry 479 */ 480 public boolean isChangeModRdn() 481 { 482 return changeType == ChangeType.ModRdn; 483 } 484 485 /** 486 * @return True if the entry is a MODIFY entry 487 */ 488 public boolean isChangeModify() 489 { 490 return changeType == ChangeType.Modify; 491 } 492 493 /** 494 * Tells if the current entry is a added one 495 * 496 * @return <code>true</code> if the entry is added 497 */ 498 public boolean isEntry() 499 { 500 return changeType == ChangeType.Add; 501 } 502 503 /** 504 * @return The associated control, if any 505 */ 506 public Control getControl() 507 { 508 return control; 509 } 510 511 /** 512 * Add a control to the entry 513 * 514 * @param control 515 * The control 516 */ 517 public void setControl( Control control ) 518 { 519 this.control = control; 520 } 521 522 /** 523 * Clone method 524 * @return a clone of the current instance 525 * @exception CloneNotSupportedException If there is some problem while cloning the instance 526 */ 527 public LdifEntry clone() throws CloneNotSupportedException 528 { 529 LdifEntry clone = (LdifEntry) super.clone(); 530 531 if ( modificationList != null ) 532 { 533 for ( Modification modif:modificationList ) 534 { 535 Modification modifClone = new ClientModification( modif.getOperation(), 536 (EntryAttribute) modif.getAttribute().clone() ); 537 clone.modificationList.add( modifClone ); 538 } 539 } 540 541 if ( modificationItems != null ) 542 { 543 for ( String key:modificationItems.keySet() ) 544 { 545 Modification modif = modificationItems.get( key ); 546 Modification modifClone = new ClientModification( modif.getOperation(), 547 (EntryAttribute) modif.getAttribute().clone() ); 548 clone.modificationItems.put( key, modifClone ); 549 } 550 551 } 552 553 if ( entry != null ) 554 { 555 clone.entry = entry.clone(); 556 } 557 558 return clone; 559 } 560 561 /** 562 * Dumps the attributes 563 * @return A String representing the attributes 564 */ 565 private String dumpAttributes() 566 { 567 StringBuffer sb = new StringBuffer(); 568 569 for ( EntryAttribute attribute:entry ) 570 { 571 if ( attribute == null ) 572 { 573 sb.append( " Null attribute\n" ); 574 continue; 575 } 576 577 sb.append( " ").append( attribute.getId() ).append( ":\n" ); 578 579 for ( Value<?> value:attribute ) 580 { 581 if ( !value.isBinary() ) 582 { 583 sb.append( " " ).append( value.getString() ).append('\n' ); 584 } 585 else 586 { 587 sb.append( " " ).append( StringTools.dumpBytes( value.getBytes() ) ).append('\n' ); 588 } 589 } 590 } 591 592 return sb.toString(); 593 } 594 595 /** 596 * Dumps the modifications 597 * @return A String representing the modifications 598 */ 599 private String dumpModificationItems() 600 { 601 StringBuffer sb = new StringBuffer(); 602 603 for ( Modification modif:modificationList ) 604 { 605 sb.append( " Operation: " ); 606 607 switch ( modif.getOperation() ) 608 { 609 case ADD_ATTRIBUTE : 610 sb.append( "ADD\n" ); 611 break; 612 613 case REMOVE_ATTRIBUTE : 614 sb.append( "REMOVE\n" ); 615 break; 616 617 case REPLACE_ATTRIBUTE : 618 sb.append( "REPLACE \n" ); 619 break; 620 621 default : 622 break; // Do nothing 623 } 624 625 EntryAttribute attribute = modif.getAttribute(); 626 627 sb.append( " Attribute: " ).append( attribute.getId() ).append( '\n' ); 628 629 if ( attribute.size() != 0 ) 630 { 631 for ( Value<?> value:attribute ) 632 { 633 if ( !value.isBinary() ) 634 { 635 sb.append( " " ).append( value.getString() ).append('\n' ); 636 } 637 else 638 { 639 sb.append( " " ).append( StringTools.dumpBytes( value.getBytes() ) ).append('\n' ); 640 } 641 } 642 } 643 } 644 645 return sb.toString(); 646 } 647 648 649 /** 650 * @return a String representing the Entry, as a LDIF 651 */ 652 public String toString() 653 { 654 try 655 { 656 return LdifUtils.convertToLdif( this ); 657 } 658 catch ( LdapException ne ) 659 { 660 return null; 661 } 662 } 663 664 665 /** 666 * @see Object#hashCode() 667 * 668 * @return the instance's hash code 669 */ 670 public int hashCode() 671 { 672 int result = 37; 673 674 if ( entry.getDn() != null ) 675 { 676 result = result*17 + entry.getDn().hashCode(); 677 } 678 679 if ( changeType != null ) 680 { 681 result = result*17 + changeType.hashCode(); 682 683 // Check each different cases 684 switch ( changeType ) 685 { 686 case Add : 687 // Checks the attributes 688 if ( entry != null ) 689 { 690 result = result * 17 + entry.hashCode(); 691 } 692 693 break; 694 695 case Delete : 696 // Nothing to compute 697 break; 698 699 case Modify : 700 if ( modificationList != null ) 701 { 702 result = result * 17 + modificationList.hashCode(); 703 704 for ( Modification modification:modificationList ) 705 { 706 result = result * 17 + modification.hashCode(); 707 } 708 } 709 710 break; 711 712 case ModDn : 713 case ModRdn : 714 result = result * 17 + ( deleteOldRdn ? 1 : -1 ); 715 716 if ( newRdn != null ) 717 { 718 result = result*17 + newRdn.hashCode(); 719 } 720 721 if ( newSuperior != null ) 722 { 723 result = result*17 + newSuperior.hashCode(); 724 } 725 726 break; 727 728 default : 729 break; // do nothing 730 } 731 } 732 733 if ( control != null ) 734 { 735 result = result * 17 + control.hashCode(); 736 } 737 738 return result; 739 } 740 741 /** 742 * @see Object#equals(Object) 743 * @return <code>true</code> if both values are equal 744 */ 745 public boolean equals( Object o ) 746 { 747 // Basic equals checks 748 if ( this == o ) 749 { 750 return true; 751 } 752 753 if ( o == null ) 754 { 755 return false; 756 } 757 758 if ( ! (o instanceof LdifEntry ) ) 759 { 760 return false; 761 } 762 763 LdifEntry otherEntry = (LdifEntry)o; 764 765 // Check the DN 766 DN thisDn = entry.getDn(); 767 DN dnEntry = otherEntry.getDn(); 768 769 if ( !thisDn.equals( dnEntry ) ) 770 { 771 return false; 772 } 773 774 775 // Check the changeType 776 if ( changeType != otherEntry.changeType ) 777 { 778 return false; 779 } 780 781 // Check each different cases 782 switch ( changeType ) 783 { 784 case Add : 785 // Checks the attributes 786 if ( entry == null ) 787 { 788 if ( otherEntry.entry != null ) 789 { 790 return false; 791 } 792 else 793 { 794 break; 795 } 796 } 797 798 if ( otherEntry.entry == null ) 799 { 800 return false; 801 } 802 803 if ( entry.size() != otherEntry.entry.size() ) 804 { 805 return false; 806 } 807 808 if ( !entry.equals( otherEntry.entry ) ) 809 { 810 return false; 811 } 812 813 break; 814 815 case Delete : 816 // Nothing to do, if the DNs are equals 817 break; 818 819 case Modify : 820 // Check the modificationItems list 821 822 // First, deal with special cases 823 if ( modificationList == null ) 824 { 825 if ( otherEntry.modificationList != null ) 826 { 827 return false; 828 } 829 else 830 { 831 break; 832 } 833 } 834 835 if ( otherEntry.modificationList == null ) 836 { 837 return false; 838 } 839 840 if ( modificationList.size() != otherEntry.modificationList.size() ) 841 { 842 return false; 843 } 844 845 // Now, compares the contents 846 int i = 0; 847 848 for ( Modification modification:modificationList ) 849 { 850 if ( ! modification.equals( otherEntry.modificationList.get( i ) ) ) 851 { 852 return false; 853 } 854 855 i++; 856 } 857 858 break; 859 860 case ModDn : 861 case ModRdn : 862 // Check the deleteOldRdn flag 863 if ( deleteOldRdn != otherEntry.deleteOldRdn ) 864 { 865 return false; 866 } 867 868 // Check the newRdn value 869 try 870 { 871 RDN thisNewRdn = new RDN( newRdn ); 872 RDN entryNewRdn = new RDN( otherEntry.newRdn ); 873 874 if ( !thisNewRdn.equals( entryNewRdn ) ) 875 { 876 return false; 877 } 878 } 879 catch ( LdapInvalidDnException ine ) 880 { 881 return false; 882 } 883 884 // Check the newSuperior value 885 try 886 { 887 DN thisNewSuperior = new DN( newSuperior ); 888 DN entryNewSuperior = new DN( otherEntry.newSuperior ); 889 890 if ( ! thisNewSuperior.equals( entryNewSuperior ) ) 891 { 892 return false; 893 } 894 } 895 catch ( LdapInvalidDnException ine ) 896 { 897 return false; 898 } 899 900 break; 901 902 default : 903 break; // do nothing 904 } 905 906 if ( control != null ) 907 { 908 return control.equals( otherEntry.control ); 909 } 910 else 911 { 912 return otherEntry.control == null; 913 } 914 } 915 916 917 /** 918 * @see Externalizable#readExternal(ObjectInput) 919 * 920 * @param in The stream from which the LdifEntry is read 921 * @throws IOException If the stream can't be read 922 * @throws ClassNotFoundException If the LdifEntry can't be created 923 */ 924 public void readExternal( ObjectInput in ) throws IOException , ClassNotFoundException 925 { 926 // Read the changeType 927 int type = in.readInt(); 928 changeType = ChangeType.getChangeType( type ); 929 entry = (Entry)in.readObject(); 930 931 switch ( changeType ) 932 { 933 case Add : 934 // Fallback 935 case Delete : 936 // we don't have anything to read, but the control 937 break; 938 939 case ModDn : 940 // Fallback 941 case ModRdn : 942 deleteOldRdn = in.readBoolean(); 943 944 if ( in.readBoolean() ) 945 { 946 newRdn = in.readUTF(); 947 } 948 949 if ( in.readBoolean() ) 950 { 951 newSuperior = in.readUTF(); 952 } 953 954 break; 955 956 case Modify : 957 // Read the modification 958 int nbModifs = in.readInt(); 959 960 961 for ( int i = 0; i < nbModifs; i++ ) 962 { 963 int operation = in.readInt(); 964 String modStr = in.readUTF(); 965 DefaultClientAttribute value = (DefaultClientAttribute)in.readObject(); 966 967 addModificationItem( ModificationOperation.getOperation( operation ), modStr, value ); 968 } 969 970 break; 971 } 972 973 if ( in.available() > 0 ) 974 { 975 // We have a control 976 control = (Control)in.readObject(); 977 } 978 } 979 980 981 /** 982 * @see Externalizable#readExternal(ObjectInput)<p> 983 * 984 *@param out The stream in which the ChangeLogEvent will be serialized. 985 * 986 *@throws IOException If the serialization fail 987 */ 988 public void writeExternal( ObjectOutput out ) throws IOException 989 { 990 // Write the changeType 991 out.writeInt( changeType.getChangeType() ); 992 993 // Write the entry 994 out.writeObject( entry ); 995 996 // Write the data 997 switch ( changeType ) 998 { 999 case Add : 1000 // Fallback 1001 case Delete : 1002 // we don't have anything to write, but the control 1003 break; 1004 1005 case ModDn : 1006 // Fallback 1007 case ModRdn : 1008 out.writeBoolean( deleteOldRdn ); 1009 1010 if ( newRdn != null ) 1011 { 1012 out.writeBoolean( true ); 1013 out.writeUTF( newRdn ); 1014 } 1015 else 1016 { 1017 out.writeBoolean( false ); 1018 } 1019 1020 if ( newSuperior != null ) 1021 { 1022 out.writeBoolean( true ); 1023 out.writeUTF( newSuperior ); 1024 } 1025 else 1026 { 1027 out.writeBoolean( false ); 1028 } 1029 break; 1030 1031 case Modify : 1032 // Read the modification 1033 out.writeInt( modificationList.size() ); 1034 1035 for ( Modification modification:modificationList ) 1036 { 1037 out.writeInt( modification.getOperation().getValue() ); 1038 out.writeUTF( modification.getAttribute().getId() ); 1039 1040 EntryAttribute attribute = modification.getAttribute(); 1041 out.writeObject( attribute ); 1042 } 1043 1044 break; 1045 } 1046 1047 if ( control != null ) 1048 { 1049 // Write the control 1050 out.writeObject( control ); 1051 1052 } 1053 1054 // and flush the result 1055 out.flush(); 1056 } 1057 }