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    package org.apache.directory.shared.ldap.codec.modify;
021    
022    
023    import java.nio.BufferOverflowException;
024    import java.nio.ByteBuffer;
025    import java.util.ArrayList;
026    import java.util.LinkedList;
027    import java.util.List;
028    
029    import org.apache.directory.shared.asn1.ber.tlv.TLV;
030    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
031    import org.apache.directory.shared.asn1.ber.tlv.Value;
032    import org.apache.directory.shared.asn1.codec.EncoderException;
033    import org.apache.directory.shared.ldap.codec.LdapConstants;
034    import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
035    import org.apache.directory.shared.ldap.entry.EntryAttribute;
036    import org.apache.directory.shared.ldap.entry.Modification;
037    import org.apache.directory.shared.ldap.entry.ModificationOperation;
038    import org.apache.directory.shared.ldap.entry.client.ClientModification;
039    import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
040    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
041    import org.apache.directory.shared.ldap.name.LdapDN;
042    import org.apache.directory.shared.ldap.util.StringTools;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    
047    /**
048     * A ModifyRequest Message. 
049     * 
050     * Its syntax is : 
051     * 
052     * ModifyRequest ::= [APPLICATION 6] SEQUENCE { 
053     *     object LDAPDN, 
054     *     modification SEQUENCE OF SEQUENCE { 
055     *         operation ENUMERATED { 
056     *             add (0), 
057     *             delete (1), 
058     *             replace (2) 
059     *         }, 
060     *         modification AttributeTypeAndValues 
061     *     } 
062     * } 
063     * 
064     * AttributeTypeAndValues ::= SEQUENCE {
065     *     type AttributeDescription, 
066     *     vals SET OF AttributeValue 
067     * } 
068     * 
069     * AttributeValue ::= OCTET STRING
070     * 
071     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
072     * @version $Rev: 798550 $, $Date: 2009-07-28 16:54:01 +0200 (Mar, 28 jul 2009) $, 
073     */
074    public class ModifyRequestCodec extends LdapMessageCodec
075    {
076        // ~ Static fields/initializers
077        // -----------------------------------------------------------------
078    
079        /** The logger */
080        private static final Logger LOG = LoggerFactory.getLogger( ModifyRequestCodec.class );
081    
082        // ~ Instance fields
083        // ----------------------------------------------------------------------------
084    
085        /** The DN to be modified. */
086        private LdapDN object;
087    
088        /** The modifications list. This is an array of Modification. */
089        private List<Modification> modifications;
090    
091        /** The current attribute being decoded */
092        private EntryAttribute currentAttribute;
093    
094        /** A local storage for the operation */
095        private ModificationOperation currentOperation;
096    
097        /** The modify request length */
098        private int modifyRequestLength;
099    
100        /** The modifications length */
101        private int modificationsLength;
102    
103        /** The modification sequence length */
104        private List<Integer> modificationSequenceLength;
105    
106        /** The list of all modification length */
107        private List<Integer> modificationLength;
108    
109        /** The list of all vals length */
110        private List<Integer> valuesLength;
111    
112    
113        // ~ Constructors
114        // -------------------------------------------------------------------------------
115    
116        /**
117         * Creates a new ModifyRequest object.
118         */
119        public ModifyRequestCodec()
120        {
121            super();
122        }
123    
124    
125        // ~ Methods
126        // ------------------------------------------------------------------------------------
127    
128        /**
129         * Get the message type
130         * 
131         * @return Returns the type.
132         */
133        public int getMessageType()
134        {
135            return LdapConstants.MODIFY_REQUEST;
136        }
137    
138    
139        /**
140         * Initialize the ArrayList for modifications.
141         */
142        public void initModifications()
143        {
144            modifications = new ArrayList<Modification>();
145        }
146    
147    
148        /**
149         * Get the entry's attributes
150         * 
151         * @return Returns the modifications.
152         */
153        public List<Modification> getModifications()
154        {
155            return modifications;
156        }
157    
158    
159        /**
160         * Add a new modification to the list
161         * 
162         * @param operation The type of operation (add, delete or replace)
163         */
164        public void addModification( int operation )
165        {
166            currentOperation = ModificationOperation.getOperation( operation );
167    
168            if ( currentAttribute == null )
169            {
170                modifications = new ArrayList<Modification>();
171            }
172        }
173    
174    
175        /**
176         * Add a new attributeTypeAndValue
177         * 
178         * @param type The attribute's name
179         */
180        public void addAttributeTypeAndValues( String type )
181        {
182            currentAttribute = new DefaultClientAttribute( type );
183    
184            Modification modification = new ClientModification( currentOperation, currentAttribute );
185            modifications.add( modification );
186        }
187    
188    
189        /**
190         * Add a new value to the current attribute
191         * 
192         * @param value The value to add
193         */
194        public void addAttributeValue( String value )
195        {
196            currentAttribute.add( value );
197        }
198    
199    
200        /**
201         * Add a new value to the current attribute
202         * 
203         * @param value The value to add
204         */
205        public void addAttributeValue( org.apache.directory.shared.ldap.entry.Value<?> value )
206        {
207            currentAttribute.add( value );
208        }
209    
210    
211        /**
212         * Add a new value to the current attribute
213         * 
214         * @param value The value to add
215         */
216        public void addAttributeValue( byte[] value )
217        {
218            currentAttribute.add( value );
219        }
220    
221    
222        /**
223         * Return the current attribute's type
224         */
225        public String getCurrentAttributeType()
226        {
227            return currentAttribute.getId();
228        }
229    
230    
231        /**
232         * Get the modification's DN
233         * 
234         * @return Returns the object.
235         */
236        public LdapDN getObject()
237        {
238            return object;
239        }
240    
241    
242        /**
243         * Set the modification DN.
244         * 
245         * @param object The DN to set.
246         */
247        public void setObject( LdapDN object )
248        {
249            this.object = object;
250        }
251    
252    
253        /**
254         * Get the current operation
255         * 
256         * @return Returns the currentOperation.
257         */
258        public int getCurrentOperation()
259        {
260            return currentOperation.getValue();
261        }
262    
263    
264        /**
265         * Store the current operation
266         * 
267         * @param currentOperation The currentOperation to set.
268         */
269        public void setCurrentOperation( int currentOperation )
270        {
271            this.currentOperation = ModificationOperation.getOperation( currentOperation );
272        }
273    
274    
275        /**
276         * Store the current operation
277         * 
278         * @param currentOperation The currentOperation to set.
279         */
280        public void setCurrentOperation( ModificationOperation currentOperation )
281        {
282            this.currentOperation = currentOperation;
283        }
284    
285    
286        /**
287         * sets the modifications
288         * 
289         * @param modifications the list of modifications
290         */
291        public void setModifications( List<Modification> modifications )
292        {
293            this.modifications = modifications;
294        }
295    
296    
297        /**
298         * Compute the ModifyRequest length 
299         * 
300         * ModifyRequest :
301         * 
302         * 0x66 L1
303         *  |
304         *  +--> 0x04 L2 object
305         *  +--> 0x30 L3 modifications
306         *        |
307         *        +--> 0x30 L4-1 modification sequence
308         *        |     |
309         *        |     +--> 0x0A 0x01 (0..2) operation
310         *        |     +--> 0x30 L5-1 modification
311         *        |           |
312         *        |           +--> 0x04 L6-1 type
313         *        |           +--> 0x31 L7-1 vals
314         *        |                 |
315         *        |                 +--> 0x04 L8-1-1 attributeValue
316         *        |                 +--> 0x04 L8-1-2 attributeValue
317         *        |                 +--> ...
318         *        |                 +--> 0x04 L8-1-i attributeValue
319         *        |                 +--> ...
320         *        |                 +--> 0x04 L8-1-n attributeValue
321         *        |
322         *        +--> 0x30 L4-2 modification sequence
323         *        .     |
324         *        .     +--> 0x0A 0x01 (0..2) operation
325         *        .     +--> 0x30 L5-2 modification
326         *                    |
327         *                    +--> 0x04 L6-2 type
328         *                    +--> 0x31 L7-2 vals
329         *                          |
330         *                          +--> 0x04 L8-2-1 attributeValue
331         *                          +--> 0x04 L8-2-2 attributeValue
332         *                          +--> ...
333         *                          +--> 0x04 L8-2-i attributeValue
334         *                          +--> ...
335         *                          +--> 0x04 L8-2-n attributeValue
336         */
337        public int computeLength()
338        {
339            // Initialized with object
340            modifyRequestLength = 1 + TLV.getNbBytes( LdapDN.getNbBytes( object ) ) + LdapDN.getNbBytes( object );
341    
342            // Modifications
343            modificationsLength = 0;
344    
345            if ( ( modifications != null ) && ( modifications.size() != 0 ) )
346            {
347                modificationSequenceLength = new LinkedList<Integer>();
348                modificationLength = new LinkedList<Integer>();
349                valuesLength = new LinkedList<Integer>();
350    
351                for ( Modification modification:modifications )
352                {
353                    // Modification sequence length initialized with the operation
354                    int localModificationSequenceLength = 1 + 1 + 1;
355                    int localValuesLength = 0;
356    
357                    // Modification length initialized with the type
358                    int typeLength = modification.getAttribute().getId().length();
359                    int localModificationLength = 1 + TLV.getNbBytes( typeLength ) + typeLength;
360    
361                    // Get all the values
362                    if ( modification.getAttribute().size() != 0 )
363                    {
364                        for ( org.apache.directory.shared.ldap.entry.Value<?> value:modification.getAttribute() )
365                        {
366                            localValuesLength += 1 + TLV.getNbBytes( value.getBytes().length )
367                                + value.getBytes().length;
368                        }
369                    }
370    
371                    localModificationLength += 1 + TLV.getNbBytes( localValuesLength ) + localValuesLength;
372    
373                    // Compute the modificationSequenceLength
374                    localModificationSequenceLength += 1 + TLV.getNbBytes( localModificationLength )
375                        + localModificationLength;
376    
377                    // Add the tag and the length
378                    modificationsLength += 1 + TLV.getNbBytes( localModificationSequenceLength )
379                        + localModificationSequenceLength;
380    
381                    // Store the arrays of values
382                    valuesLength.add( localValuesLength );
383                    modificationLength.add( localModificationLength );
384                    modificationSequenceLength.add( localModificationSequenceLength );
385                }
386    
387                // Add the modifications length to the modificationRequestLength
388                modifyRequestLength += 1 + TLV.getNbBytes( modificationsLength ) + modificationsLength;
389            }
390    
391            return 1 + TLV.getNbBytes( modifyRequestLength ) + modifyRequestLength;
392        }
393    
394    
395        /**
396         * Encode the ModifyRequest message to a PDU. 
397         * 
398         * ModifyRequest : 
399         * 0x66 LL
400         *   0x04 LL object
401         *   0x30 LL modifiations
402         *     0x30 LL modification sequence
403         *       0x0A 0x01 operation
404         *       0x30 LL modification
405         *         0x04 LL type
406         *         0x31 LL vals
407         *           0x04 LL attributeValue
408         *           ... 
409         *           0x04 LL attributeValue
410         *     ... 
411         *     0x30 LL modification sequence
412         *       0x0A 0x01 operation
413         *       0x30 LL modification
414         *         0x04 LL type
415         *         0x31 LL vals
416         *           0x04 LL attributeValue
417         *           ... 
418         *           0x04 LL attributeValue
419         * 
420         * 
421         * @param buffer The buffer where to put the PDU
422         * @return The PDU.
423         */
424        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
425        {
426            if ( buffer == null )
427            {
428                throw new EncoderException( "Cannot put a PDU in a null buffer !" );
429            }
430    
431            try
432            {
433                // The AddRequest Tag
434                buffer.put( LdapConstants.MODIFY_REQUEST_TAG );
435                buffer.put( TLV.getBytes( modifyRequestLength ) );
436    
437                // The entry
438                Value.encode( buffer, LdapDN.getBytes( object ) );
439    
440                // The modifications sequence
441                buffer.put( UniversalTag.SEQUENCE_TAG );
442                buffer.put( TLV.getBytes( modificationsLength ) );
443    
444                // The modifications list
445                if ( ( modifications != null ) && ( modifications.size() != 0 ) )
446                {
447                    int modificationNumber = 0;
448    
449                    // Compute the modifications length
450                    for ( Modification modification:modifications )
451                    {
452                        // The modification sequence
453                        buffer.put( UniversalTag.SEQUENCE_TAG );
454                        int localModificationSequenceLength = modificationSequenceLength
455                            .get( modificationNumber );
456                        buffer.put( TLV.getBytes( localModificationSequenceLength ) );
457    
458                        // The operation. The value has to be changed, it's not
459                        // the same value in DirContext and in RFC 2251.
460                        buffer.put( UniversalTag.ENUMERATED_TAG );
461                        buffer.put( ( byte ) 1 );
462                        buffer.put( ( byte ) modification.getOperation().getValue() );
463    
464                        // The modification
465                        buffer.put( UniversalTag.SEQUENCE_TAG );
466                        int localModificationLength = modificationLength.get( modificationNumber );
467                        buffer.put( TLV.getBytes( localModificationLength ) );
468    
469                        // The modification type
470                        Value.encode( buffer, modification.getAttribute().getId() );
471    
472                        // The values
473                        buffer.put( UniversalTag.SET_TAG );
474                        int localValuesLength = valuesLength.get( modificationNumber );
475                        buffer.put( TLV.getBytes( localValuesLength ) );
476    
477                        if ( modification.getAttribute().size() != 0 )
478                        {
479                            for ( org.apache.directory.shared.ldap.entry.Value<?> value:modification.getAttribute() )
480                            {
481                                if ( !value.isBinary() )
482                                {
483                                    Value.encode( buffer, value.getString() );
484                                }
485                                else
486                                {
487                                    Value.encode( buffer, value.getBytes() );
488                                }
489                            }
490                        }
491    
492                        // Go to the next modification number;
493                        modificationNumber++;
494                    }
495                }
496            }
497            catch ( BufferOverflowException boe )
498            {
499                throw new EncoderException( "The PDU buffer size is too small !" );
500            }
501    
502            return buffer;
503        }
504    
505    
506        /**
507         * Get a String representation of a ModifyRequest
508         * 
509         * @return A ModifyRequest String
510         */
511        public String toString()
512        {
513            StringBuffer sb = new StringBuffer();
514    
515            sb.append( "    Modify Request\n" );
516            sb.append( "        Object : '" ).append( object ).append( "'\n" );
517    
518            if ( modifications != null )
519            {
520                int i = 0;
521                
522                for ( Modification modification:modifications )
523                {
524                    sb.append( "            Modification[" ).append( i ).append( "]\n" );
525                    sb.append( "                Operation : " );
526    
527                    if ( modification != null )
528                    {
529                        switch ( modification.getOperation() )
530                        {
531        
532                            case ADD_ATTRIBUTE:
533                                sb.append( " add\n" );
534                                break;
535        
536                            case REPLACE_ATTRIBUTE:
537                                sb.append( " replace\n" );
538                                break;
539        
540                            case REMOVE_ATTRIBUTE:
541                                sb.append( " delete\n" );
542                                break;
543                        }
544    
545                        sb.append( "                Modification\n" );
546        
547                        EntryAttribute attribute = modification.getAttribute();
548        
549                        if ( attribute != null )
550                        {
551                            sb.append( attribute );
552                        }
553                    }
554                    else
555                    {
556                        sb.append( " unknown modification operation\n" );
557                    }
558    
559                }
560            }
561    
562            return sb.toString();
563        }
564    }