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 }