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.search;
021    
022    
023    import java.nio.BufferOverflowException;
024    import java.nio.ByteBuffer;
025    import java.util.LinkedList;
026    import java.util.List;
027    
028    import javax.naming.NamingException;
029    
030    import org.apache.directory.shared.asn1.ber.tlv.TLV;
031    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
032    import org.apache.directory.shared.asn1.ber.tlv.Value;
033    import org.apache.directory.shared.asn1.codec.EncoderException;
034    import org.apache.directory.shared.asn1.util.Asn1StringUtils;
035    import org.apache.directory.shared.ldap.codec.LdapConstants;
036    import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
037    import org.apache.directory.shared.ldap.entry.Entry;
038    import org.apache.directory.shared.ldap.entry.EntryAttribute;
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.entry.client.DefaultClientEntry;
042    import org.apache.directory.shared.ldap.name.LdapDN;
043    import org.apache.directory.shared.ldap.util.StringTools;
044    
045    
046    /**
047     * A SearchResultEntry Message. Its syntax is :
048     *   SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
049     *       objectName      LDAPDN,
050     *       attributes      PartialAttributeList }
051     * 
052     *   PartialAttributeList ::= SEQUENCE OF SEQUENCE {
053     *       type    AttributeDescription,
054     *       vals    SET OF AttributeValue }
055     * 
056     *   AttributeDescription ::= LDAPString
057     * 
058     *   AttributeValue ::= OCTET STRING
059     * 
060     * It contains an entry, with all its attributes, and all the attributes
061     * values. If a search request is submited, all the results are sent one
062     * by one, followed by a searchResultDone message.
063     * 
064     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
065     * @version $Rev: 798550 $, $Date: 2009-07-28 16:54:01 +0200 (Mar, 28 jul 2009) $, 
066     */
067    public class SearchResultEntryCodec extends LdapMessageCodec
068    {
069        // ~ Instance fields
070        // ----------------------------------------------------------------------------
071    
072        /** A temporary storage for the byte[] representing the objectName */
073        private byte[] objectNameBytes;
074    
075        /** The entry */
076        private Entry entry = new DefaultClientEntry();
077    
078        /** The current attribute being decoded */
079        private EntryAttribute currentAttributeValue;
080    
081        /** The search result entry length */
082        private int searchResultEntryLength;
083    
084        /** The partial attributes length */
085        private int attributesLength;
086    
087        /** The list of all attributes length */
088        private List<Integer> attributeLength;
089    
090        /** The list of all vals length */
091        private List<Integer> valsLength;
092    
093    
094        // ~ Constructors
095        // -------------------------------------------------------------------------------
096    
097        /**
098         * Creates a new SearchResultEntry object.
099         */
100        public SearchResultEntryCodec()
101        {
102            super();
103        }
104    
105    
106        // ~ Methods
107        // ------------------------------------------------------------------------------------
108    
109        /**
110         * Get the message type
111         * 
112         * @return Returns the type.
113         */
114        public int getMessageType()
115        {
116            return LdapConstants.SEARCH_RESULT_ENTRY;
117        }
118    
119    
120        /**
121         * Get the entry DN
122         * 
123         * @return Returns the objectName.
124         */
125        public LdapDN getObjectName()
126        {
127            return entry.getDn();
128        }
129    
130    
131        /**
132         * Set the entry DN.
133         * 
134         * @param objectName The objectName to set.
135         */
136        public void setObjectName( LdapDN objectName )
137        {
138            entry.setDn( objectName );
139        }
140    
141    
142        /**
143         * Get the entry.
144         * 
145         * @return Returns the entry
146         */
147        public Entry getEntry()
148        {
149            return entry;
150        }
151    
152    
153        /**
154         * Sets the entry.
155         *
156         * @param entry
157         *      the entry
158         */
159        public void setEntry( Entry entry )
160        {
161            this.entry = entry;
162        }
163    
164    
165        /**
166         * Create a new attributeValue
167         * 
168         * @param type The attribute's name
169         */
170        public void addAttributeValues( String type )
171        {
172            currentAttributeValue = new DefaultClientAttribute( type );
173    
174            try
175            {
176                entry.put( currentAttributeValue );
177            }
178            catch ( NamingException ne )
179            {
180                // Too bad... But there is nothing we can do.
181            }
182        }
183    
184    
185        /**
186         * Add a new value to the current attribute
187         * 
188         * @param value
189         */
190        public void addAttributeValue( Object value )
191        {
192            if ( value instanceof String )
193            {
194                currentAttributeValue.add( ( String ) value );
195            }
196            else
197            {
198                currentAttributeValue.add( ( byte[] ) value );
199            }
200        }
201    
202    
203        /**
204         * Compute the SearchResultEntry length
205         * 
206         * SearchResultEntry :
207         * 
208         * 0x64 L1
209         *  |
210         *  +--> 0x04 L2 objectName
211         *  +--> 0x30 L3 (attributes)
212         *        |
213         *        +--> 0x30 L4-1 (partial attributes list)
214         *        |     |
215         *        |     +--> 0x04 L5-1 type
216         *        |     +--> 0x31 L6-1 (values)
217         *        |           |
218         *        |           +--> 0x04 L7-1-1 value
219         *        |           +--> ...
220         *        |           +--> 0x04 L7-1-n value
221         *        |
222         *        +--> 0x30 L4-2 (partial attributes list)
223         *        |     |
224         *        |     +--> 0x04 L5-2 type
225         *        |     +--> 0x31 L6-2 (values)
226         *        |           |
227         *        |           +--> 0x04 L7-2-1 value
228         *        |           +--> ...
229         *        |           +--> 0x04 L7-2-n value
230         *        |
231         *        +--> ...
232         *        |
233         *        +--> 0x30 L4-m (partial attributes list)
234         *              |
235         *              +--> 0x04 L5-m type
236         *              +--> 0x31 L6-m (values)
237         *                    |
238         *                    +--> 0x04 L7-m-1 value
239         *                    +--> ...
240         *                    +--> 0x04 L7-m-n value
241         * 
242         */
243        public int computeLength()
244        {
245            objectNameBytes = StringTools.getBytesUtf8( entry.getDn().getUpName() );
246    
247            // The entry
248            searchResultEntryLength = 1 + TLV.getNbBytes( objectNameBytes.length ) + objectNameBytes.length;
249    
250            // The attributes sequence
251            attributesLength = 0;
252    
253            if ( ( entry != null ) && ( entry.size() != 0 ) )
254            {
255                attributeLength = new LinkedList<Integer>();
256                valsLength = new LinkedList<Integer>();
257    
258                // Compute the attributes length
259                for ( EntryAttribute attribute : entry )
260                {
261                    int localAttributeLength = 0;
262                    int localValuesLength = 0;
263    
264                    // Get the type length
265                    int idLength = attribute.getId().getBytes().length;
266                    localAttributeLength = 1 + TLV.getNbBytes( idLength ) + idLength;
267    
268                    if ( attribute.size() != 0 )
269                    {
270                        // The values
271                        if ( attribute.size() > 0 )
272                        {
273                            localValuesLength = 0;
274    
275                            for ( org.apache.directory.shared.ldap.entry.Value<?> value : attribute )
276                            {
277                                byte[] binaryValue = value.getBytes();
278                                localValuesLength += 1 + TLV.getNbBytes( binaryValue.length ) + binaryValue.length;
279                            }
280    
281                            localAttributeLength += 1 + TLV.getNbBytes( localValuesLength ) + localValuesLength;
282                        }
283                        else
284                        {
285                            // We have to deal with the special wase where
286                            // we don't have a value.
287                            // It will be encoded as an empty OCTETSTRING,
288                            // so it will be two byte slong (0x04 0x00)
289                            localAttributeLength += 1 + 1;
290                        }
291                    }
292                    else
293                    {
294                        // We have no values. We will just have an empty SET OF :
295                        // 0x31 0x00
296                        localAttributeLength += 1 + 1;
297                    }
298    
299                    // add the attribute length to the attributes length
300                    attributesLength += 1 + TLV.getNbBytes( localAttributeLength ) + localAttributeLength;
301    
302                    attributeLength.add( localAttributeLength );
303                    valsLength.add( localValuesLength );
304                }
305            }
306    
307            searchResultEntryLength += 1 + TLV.getNbBytes( attributesLength ) + attributesLength;
308    
309            // Return the result.
310            return 1 + TLV.getNbBytes( searchResultEntryLength ) + searchResultEntryLength;
311        }
312    
313    
314        /**
315         * Encode the SearchResultEntry message to a PDU.
316         * 
317         * SearchResultEntry :
318         * 
319         * 0x64 LL
320         *   0x04 LL objectName
321         *   0x30 LL attributes
322         *     0x30 LL partialAttributeList
323         *       0x04 LL type
324         *       0x31 LL vals
325         *         0x04 LL attributeValue
326         *         ... 
327         *         0x04 LL attributeValue
328         *     ... 
329         *     0x30 LL partialAttributeList
330         *       0x04 LL type
331         *       0x31 LL vals
332         *         0x04 LL attributeValue
333         *         ... 
334         *         0x04 LL attributeValue 
335         * 
336         * @param buffer The buffer where to put the PDU
337         * @return The PDU.
338         */
339        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
340        {
341            if ( buffer == null )
342            {
343                throw new EncoderException( "Cannot put a PDU in a null buffer !" );
344            }
345    
346            try
347            {
348                // The SearchResultEntry Tag
349                buffer.put( LdapConstants.SEARCH_RESULT_ENTRY_TAG );
350                buffer.put( TLV.getBytes( searchResultEntryLength ) );
351    
352                // The objectName
353                Value.encode( buffer, objectNameBytes );
354    
355                // The attributes sequence
356                buffer.put( UniversalTag.SEQUENCE_TAG );
357                buffer.put( TLV.getBytes( attributesLength ) );
358    
359                // The partial attribute list
360                if ( ( entry != null ) && ( entry.size() != 0 ) )
361                {
362                    int attributeNumber = 0;
363    
364                    // Compute the attributes length
365                    for ( EntryAttribute attribute : entry )
366                    {
367                        // The partial attribute list sequence
368                        buffer.put( UniversalTag.SEQUENCE_TAG );
369                        int localAttributeLength = attributeLength.get( attributeNumber );
370                        buffer.put( TLV.getBytes( localAttributeLength ) );
371    
372                        // The attribute type
373                        Value.encode( buffer, Asn1StringUtils.asciiStringToByte( attribute.getUpId() ) );
374    
375                        // The values
376                        buffer.put( UniversalTag.SET_TAG );
377                        int localValuesLength = valsLength.get( attributeNumber );
378                        buffer.put( TLV.getBytes( localValuesLength ) );
379    
380                        if ( attribute.size() != 0 )
381                        {
382                            if ( attribute.size() > 0 )
383                            {
384                                for ( org.apache.directory.shared.ldap.entry.Value<?> value : attribute )
385                                {
386                                    if ( !value.isBinary() )
387                                    {
388                                        Value.encode( buffer, value.getString() );
389                                    }
390                                    else
391                                    {
392                                        Value.encode( buffer, value.getBytes() );
393                                    }
394                                }
395                            }
396                        }
397    
398                        // Go to the next attribute number;
399                        attributeNumber++;
400                    }
401                }
402            }
403            catch ( BufferOverflowException boe )
404            {
405                throw new EncoderException( "The PDU buffer size is too small !" );
406            }
407    
408            return buffer;
409        }
410    
411    
412        /**
413         * Returns the Search Result Entry string
414         * 
415         * @return The Search Result Entry string
416         */
417        public String toString()
418        {
419    
420            StringBuilder sb = new StringBuilder();
421    
422            sb.append( "    Search Result Entry\n" );
423            sb.append( "        entry\n" );
424    
425            if ( ( entry == null ) || ( entry.size() == 0 ) )
426            {
427                sb.append( "            No entry\n" );
428            }
429            else
430            {
431                sb.append( entry );
432            }
433    
434            return sb.toString();
435        }
436    
437    
438        /**
439         * @return Returns the currentAttributeValue.
440         */
441        public String getCurrentAttributeValueType()
442        {
443            return currentAttributeValue.getId();
444        }
445    }