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 */
020package org.apache.directory.api.ldap.codec.decorators;
021
022
023import java.nio.BufferOverflowException;
024import java.nio.ByteBuffer;
025import java.util.LinkedList;
026import java.util.List;
027
028import org.apache.directory.api.asn1.EncoderException;
029import org.apache.directory.api.asn1.ber.tlv.BerValue;
030import org.apache.directory.api.asn1.ber.tlv.TLV;
031import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
032import org.apache.directory.api.i18n.I18n;
033import org.apache.directory.api.ldap.codec.api.LdapApiService;
034import org.apache.directory.api.ldap.codec.api.LdapCodecConstants;
035import org.apache.directory.api.ldap.codec.api.MessageDecorator;
036import org.apache.directory.api.ldap.model.entry.Attribute;
037import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
038import org.apache.directory.api.ldap.model.entry.Entry;
039import org.apache.directory.api.ldap.model.entry.Value;
040import org.apache.directory.api.ldap.model.exception.LdapException;
041import org.apache.directory.api.ldap.model.message.SearchResultEntry;
042import org.apache.directory.api.ldap.model.name.Dn;
043import org.apache.directory.api.util.Strings;
044
045
046/**
047 * A decorator for the SearchResultEntry message
048 *
049 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
050 */
051public class SearchResultEntryDecorator extends MessageDecorator<SearchResultEntry> implements SearchResultEntry
052{
053    /** A temporary storage for the byte[] representing the objectName */
054    private byte[] objectNameBytes;
055
056    /** The search result entry length */
057    private int searchResultEntryLength;
058
059    /** The partial attributes length */
060    private int attributesLength;
061
062    /** The list of all attributes length */
063    private List<Integer> attributeLength;
064
065    /** The list of all attributes Id bytes */
066    private List<byte[]> attributeIds;
067
068    /** The list of all values length */
069    private List<Integer> valuesLength;
070
071    /** The current attribute being processed */
072    private Attribute currentAttribute;
073
074
075    /**
076     * Makes a SearchResultEntry encodable.
077     *
078     * @param codec The LDAP service instance
079     * @param decoratedMessage the decorated SearchResultEntry
080     */
081    public SearchResultEntryDecorator( LdapApiService codec, SearchResultEntry decoratedMessage )
082    {
083        super( codec, decoratedMessage );
084    }
085
086
087    /**
088     * @return The current attribute
089     */
090    public Attribute getCurrentAttribute()
091    {
092        return currentAttribute;
093    }
094
095
096    /**
097     * Create a new attribute
098     * 
099     * @param type The attribute's type
100     * @throws LdapException If the value is invalid
101     */
102    public void addAttribute( String type ) throws LdapException
103    {
104        currentAttribute = new DefaultAttribute( type );
105
106        getDecorated().getEntry().put( currentAttribute );
107    }
108
109
110    /**
111     * Create a new attribute
112     * 
113     * @param type The attribute's type
114     * @throws LdapException If the value is invalid
115     */
116    public void addAttribute( byte[] type ) throws LdapException
117    {
118        currentAttribute = new DefaultAttribute( type );
119
120        getDecorated().getEntry().put( currentAttribute );
121    }
122
123
124    /**
125     * Add a new value to the current attribute
126     * 
127     * @param value The added value
128     * @throws LdapException If the value is invalid
129     */
130    public void addAttributeValue( Object value ) throws LdapException
131    {
132        if ( value instanceof String )
133        {
134            currentAttribute.add( ( String ) value );
135        }
136        else
137        {
138            currentAttribute.add( ( byte[] ) value );
139        }
140    }
141
142
143    //-------------------------------------------------------------------------
144    // The IntermediateResponse methods
145    //-------------------------------------------------------------------------
146
147    /**
148     * {@inheritDoc}
149     */
150    @Override
151    public Dn getObjectName()
152    {
153        return getDecorated().getObjectName();
154    }
155
156
157    /**
158     * {@inheritDoc}
159     */
160    @Override
161    public void setObjectName( Dn objectName )
162    {
163        getDecorated().setObjectName( objectName );
164    }
165
166
167    /**
168     * {@inheritDoc}
169     */
170    @Override
171    public Entry getEntry()
172    {
173        return getDecorated().getEntry();
174    }
175
176
177    /**
178     * {@inheritDoc}
179     */
180    @Override
181    public void setEntry( Entry entry )
182    {
183        getDecorated().setEntry( entry );
184    }
185
186
187    //-------------------------------------------------------------------------
188    // The Decorator methods
189    //-------------------------------------------------------------------------
190
191    /**
192     * Compute the SearchResultEntry length
193     * <br>
194     * SearchResultEntry :
195     * <pre>
196     * 0x64 L1
197     *  |
198     *  +--&gt; 0x04 L2 objectName
199     *  +--&gt; 0x30 L3 (attributes)
200     *        |
201     *        +--&gt; 0x30 L4-1 (partial attributes list)
202     *        |     |
203     *        |     +--&gt; 0x04 L5-1 type
204     *        |     +--&gt; 0x31 L6-1 (values)
205     *        |           |
206     *        |           +--&gt; 0x04 L7-1-1 value
207     *        |           +--&gt; ...
208     *        |           +--&gt; 0x04 L7-1-n value
209     *        |
210     *        +--&gt; 0x30 L4-2 (partial attributes list)
211     *        |     |
212     *        |     +--&gt; 0x04 L5-2 type
213     *        |     +--&gt; 0x31 L6-2 (values)
214     *        |           |
215     *        |           +--&gt; 0x04 L7-2-1 value
216     *        |           +--&gt; ...
217     *        |           +--&gt; 0x04 L7-2-n value
218     *        |
219     *        +--&gt; ...
220     *        |
221     *        +--&gt; 0x30 L4-m (partial attributes list)
222     *              |
223     *              +--&gt; 0x04 L5-m type
224     *              +--&gt; 0x31 L6-m (values)
225     *                    |
226     *                    +--&gt; 0x04 L7-m-1 value
227     *                    +--&gt; ...
228     *                    +--&gt; 0x04 L7-m-n value
229     * </pre>
230     */
231    @Override
232    public int computeLength()
233    {
234        Dn dn = getObjectName();
235
236        objectNameBytes = Strings.getBytesUtf8Ascii( dn.getName() );
237
238        // The entry
239        searchResultEntryLength = 1 + TLV.getNbBytes( objectNameBytes.length ) + objectNameBytes.length;
240
241        // The attributes sequence
242        attributesLength = 0;
243
244        Entry entry = getEntry();
245
246        if ( ( entry != null ) && ( entry.size() != 0 ) )
247        {
248            attributeLength = new LinkedList<>();
249            attributeIds = new LinkedList<>();
250            valuesLength = new LinkedList<>();
251
252            // Store those lists in the object
253            valuesLength = new LinkedList<>();
254
255            // Compute the attributes length
256            for ( Attribute attribute : entry )
257            {
258                int localAttributeLength;
259                int localValuesLength = 0;
260
261                // Get the type length
262                byte[] attributeIdBytes = Strings.getBytesUtf8Ascii( attribute.getUpId() );
263                attributeIds.add( attributeIdBytes );
264                int idLength = attributeIdBytes.length;
265                localAttributeLength = 1 + TLV.getNbBytes( idLength ) + idLength;
266
267                if ( attribute.size() != 0 )
268                {
269                    // The values
270                    if ( attribute.size() > 0 )
271                    {
272                        localValuesLength = 0;
273
274                        for ( org.apache.directory.api.ldap.model.entry.Value<?> value : attribute )
275                        {
276                            byte[] binaryValue = value.getBytes();
277                            localValuesLength += 1 + TLV.getNbBytes( binaryValue.length ) + binaryValue.length;
278                        }
279
280                        localAttributeLength += 1 + TLV.getNbBytes( localValuesLength ) + localValuesLength;
281                    }
282                    else
283                    {
284                        // We have to deal with the special case where
285                        // we don't have a value.
286                        // It will be encoded as an empty OCTETSTRING,
287                        // so it will be two bytes long (0x04 0x00)
288                        localAttributeLength += 1 + 1;
289                    }
290                }
291                else
292                {
293                    // We have no values. We will just have an empty SET OF :
294                    // 0x31 0x00
295                    localAttributeLength += 1 + 1;
296                }
297
298                // add the attribute length to the attributes length
299                attributesLength += 1 + TLV.getNbBytes( localAttributeLength ) + localAttributeLength;
300
301                // Store the lengths of the encoded attributes and values
302                attributeLength.add( localAttributeLength );
303                valuesLength.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     * <br>
317     * SearchResultEntry :
318     * <pre>
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     * </pre>
336     * 
337     * @param buffer The buffer where to put the PDU
338     * @return The PDU.
339     */
340    @Override
341    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
342    {
343        try
344        {
345            // The SearchResultEntry Tag
346            buffer.put( LdapCodecConstants.SEARCH_RESULT_ENTRY_TAG );
347            buffer.put( TLV.getBytes( searchResultEntryLength ) );
348
349            // The objectName
350            BerValue.encode( buffer, objectNameBytes );
351
352            // The attributes sequence
353            buffer.put( UniversalTag.SEQUENCE.getValue() );
354            buffer.put( TLV.getBytes( attributesLength ) );
355
356            // The partial attribute list
357            Entry entry = getEntry();
358
359            if ( ( entry != null ) && ( entry.size() != 0 ) )
360            {
361                int attributeNumber = 0;
362
363                // Compute the attributes length
364                for ( Attribute attribute : entry )
365                {
366                    // The partial attribute list sequence
367                    buffer.put( UniversalTag.SEQUENCE.getValue() );
368                    int localAttributeLength = attributeLength.get( attributeNumber );
369                    buffer.put( TLV.getBytes( localAttributeLength ) );
370
371                    // The attribute type
372                    BerValue.encode( buffer, attributeIds.get( attributeNumber ) );
373
374                    // The values
375                    buffer.put( UniversalTag.SET.getValue() );
376                    int localValuesLength = valuesLength.get( attributeNumber );
377                    buffer.put( TLV.getBytes( localValuesLength ) );
378
379                    if ( attribute.size() > 0 )
380                    {
381                        for ( Value<?> value : attribute )
382                        {
383                            BerValue.encode( buffer, value.getBytes() );
384                        }
385                    }
386
387                    // Go to the next attribute number
388                    attributeNumber++;
389                }
390            }
391        }
392        catch ( BufferOverflowException boe )
393        {
394            throw new EncoderException( I18n.err( I18n.ERR_04005 ), boe );
395        }
396
397        return buffer;
398    }
399}