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