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