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