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.extras.controls.syncrepl_impl;
021
022
023import java.nio.ByteBuffer;
024import java.util.ArrayList;
025import java.util.List;
026
027import org.apache.directory.api.asn1.Asn1Object;
028import org.apache.directory.api.asn1.DecoderException;
029import org.apache.directory.api.asn1.EncoderException;
030import org.apache.directory.api.asn1.ber.Asn1Decoder;
031import org.apache.directory.api.asn1.ber.tlv.BerValue;
032import org.apache.directory.api.asn1.ber.tlv.TLV;
033import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.codec.api.ControlDecorator;
036import org.apache.directory.api.ldap.codec.api.LdapApiService;
037import org.apache.directory.api.ldap.extras.controls.syncrepl.syncInfoValue.SyncInfoValue;
038import org.apache.directory.api.ldap.extras.controls.syncrepl.syncInfoValue.SyncInfoValueImpl;
039import org.apache.directory.api.ldap.extras.controls.syncrepl.syncInfoValue.SynchronizationInfoEnum;
040import org.apache.directory.api.util.Strings;
041
042
043/**
044 * A syncInfoValue object, as defined in RFC 4533
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class SyncInfoValueDecorator extends ControlDecorator<SyncInfoValue> implements SyncInfoValue
049{
050    /** The syncUUIDs cumulative length */
051    private int syncUUIDsLength;
052
053    /** An instance of this decoder */
054    private static final Asn1Decoder DECODER = new Asn1Decoder();
055
056    /** The global length for this control */
057    private int syncInfoValueLength;
058
059
060    /**
061     * The constructor for this codec. Dont't forget to set the type.
062     * 
063     * @param codec The LDAP Service to use
064     */
065    public SyncInfoValueDecorator( LdapApiService codec )
066    {
067        super( codec, new SyncInfoValueImpl() );
068    }
069
070
071    /**
072     * The constructor for this codec. Dont't forget to set the type.
073     * 
074     * @param codec The LDAP Service to use
075     * @param control The SyncInfoValue to decorate
076     */
077    public SyncInfoValueDecorator( LdapApiService codec, SyncInfoValue control )
078    {
079        super( codec, control );
080    }
081
082
083    /**
084     * The constructor for this codec.
085     * 
086     * @param codec The LDAP Service to use
087     * @param type The kind of syncInfo we will store. Can be newCookie,
088     * refreshPresent, refreshDelete or syncIdSet
089     */
090    public SyncInfoValueDecorator( LdapApiService codec, SynchronizationInfoEnum type )
091    {
092        this( codec );
093
094        setType( type );
095    }
096
097    
098    /**
099     * {@inheritDoc}
100     */
101    @Override
102    public SynchronizationInfoEnum getType()
103    {
104        return getDecorated().getType();
105    }
106
107
108    /**
109     * {@inheritDoc}
110     */
111    @Override
112    public void setType( SynchronizationInfoEnum type )
113    {
114        this.getDecorated().setType( type );
115
116        // Initialize the arrayList if needed
117        if ( ( type == SynchronizationInfoEnum.SYNC_ID_SET ) && ( getDecorated().getSyncUUIDs() == null ) )
118        {
119            getDecorated().setSyncUUIDs( new ArrayList<byte[]>() );
120        }
121    }
122
123
124    /**
125     * {@inheritDoc}
126     */
127    @Override
128    public byte[] getCookie()
129    {
130        return getDecorated().getCookie();
131    }
132
133
134    /**
135     * {@inheritDoc}
136     */
137    @Override
138    public void setCookie( byte[] cookie )
139    {
140        // Copy the bytes
141        if ( !Strings.isEmpty( cookie ) )
142        {
143            byte[] copy = new byte[cookie.length];
144            System.arraycopy( cookie, 0, copy, 0, cookie.length );
145            getDecorated().setCookie( copy );
146        }
147        else
148        {
149            getDecorated().setCookie( null );
150        }
151    }
152
153
154    /**
155     * {@inheritDoc}
156     */
157    @Override
158    public boolean isRefreshDone()
159    {
160        return getDecorated().isRefreshDone();
161    }
162
163
164    /**
165     * {@inheritDoc}
166     */
167    @Override
168    public void setRefreshDone( boolean refreshDone )
169    {
170        getDecorated().setRefreshDone( refreshDone );
171    }
172
173
174    /**
175     * {@inheritDoc}
176     */
177    @Override
178    public boolean isRefreshDeletes()
179    {
180        return getDecorated().isRefreshDeletes();
181    }
182
183
184    /**
185     * {@inheritDoc}
186     */
187    @Override
188    public void setRefreshDeletes( boolean refreshDeletes )
189    {
190        getDecorated().setRefreshDeletes( refreshDeletes );
191    }
192
193
194    /**
195     * {@inheritDoc}
196     */
197    @Override
198    public List<byte[]> getSyncUUIDs()
199    {
200        return getDecorated().getSyncUUIDs();
201    }
202
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public void setSyncUUIDs( List<byte[]> syncUUIDs )
209    {
210        getDecorated().setSyncUUIDs( syncUUIDs );
211    }
212
213
214    /**
215     * {@inheritDoc}
216     */
217    @Override
218    public void addSyncUUID( byte[] syncUUID )
219    {
220        getDecorated().addSyncUUID( syncUUID );
221    }
222
223
224    /**
225     * Compute the SyncInfoValue length.
226     * <br>
227     * SyncInfoValue :
228     * <pre>
229     * 0xA0 L1 abcd                   // newCookie
230     * 0xA1 L2                        // refreshDelete
231     *   |
232     *  [+--&gt; 0x04 L3 abcd]           // cookie
233     *  [+--&gt; 0x01 0x01 (0x00|0xFF)   // refreshDone
234     * 0xA2 L4                        // refreshPresent
235     *   |
236     *  [+--&gt; 0x04 L5 abcd]           // cookie
237     *  [+--&gt; 0x01 0x01 (0x00|0xFF)   // refreshDone
238     * 0xA3 L6                        // syncIdSet
239     *   |
240     *  [+--&gt; 0x04 L7 abcd]           // cookie
241     *  [+--&gt; 0x01 0x01 (0x00|0xFF)   // refreshDeletes
242     *   +--&gt; 0x31 L8                 // SET OF syncUUIDs
243     *          |
244     *         [+--&gt; 0x04 L9 abcd]    // syncUUID
245     * </pre>
246     * 
247     * @return The computed length
248     **/
249    @Override
250    public int computeLength()
251    {
252        // The mode length
253        syncInfoValueLength = 0;
254
255        switch ( getType() )
256        {
257            case NEW_COOKIE:
258                if ( getCookie() != null )
259                {
260                    syncInfoValueLength = 1 + TLV.getNbBytes( getCookie().length ) + getCookie().length;
261                }
262                else
263                {
264                    syncInfoValueLength = 1 + 1;
265                }
266
267                valueLength = syncInfoValueLength;
268
269                // Call the super class to compute the global control length
270                return valueLength;
271
272            case REFRESH_DELETE:
273            case REFRESH_PRESENT:
274                if ( getCookie() != null )
275                {
276                    syncInfoValueLength = 1 + TLV.getNbBytes( getCookie().length ) + getCookie().length;
277                }
278
279                // The refreshDone flag, only if not true, as it default to true
280                if ( !isRefreshDone() )
281                {
282                    syncInfoValueLength += 1 + 1 + 1;
283                }
284
285                valueLength = 1 + TLV.getNbBytes( syncInfoValueLength ) + syncInfoValueLength;
286
287                // Call the super class to compute the global control length
288                return valueLength;
289
290            case SYNC_ID_SET:
291                if ( getCookie() != null )
292                {
293                    syncInfoValueLength = 1 + TLV.getNbBytes( getCookie().length ) + getCookie().length;
294                }
295
296                // The refreshDeletes flag, default to false
297                if ( isRefreshDeletes() )
298                {
299                    syncInfoValueLength += 1 + 1 + 1;
300                }
301
302                // The syncUUIDs if any
303                syncUUIDsLength = 0;
304
305                if ( !getSyncUUIDs().isEmpty() )
306                {
307                    for ( byte[] syncUUID : getSyncUUIDs() )
308                    {
309                        int uuidLength = 1 + TLV.getNbBytes( syncUUID.length ) + syncUUID.length;
310
311                        syncUUIDsLength += uuidLength;
312                    }
313                }
314
315                syncInfoValueLength += 1 + TLV.getNbBytes( syncUUIDsLength ) + syncUUIDsLength;
316                valueLength = 1 + TLV.getNbBytes( syncInfoValueLength ) + syncInfoValueLength;
317
318                // Call the super class to compute the global control length
319                return valueLength;
320
321            default:
322
323        }
324
325        return 1 + TLV.getNbBytes( syncInfoValueLength ) + syncInfoValueLength;
326    }
327
328
329    /**
330     * Encode the SyncInfoValue control
331     *
332     * @param buffer The encoded sink
333     * @return A ByteBuffer that contains the encoded PDU
334     * @throws EncoderException If anything goes wrong.
335     */
336    @Override
337    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
338    {
339        if ( buffer == null )
340        {
341            throw new EncoderException( I18n.err( I18n.ERR_04023 ) );
342        }
343
344        switch ( getType() )
345        {
346            case NEW_COOKIE:
347                // The first case : newCookie
348                buffer.put( ( byte ) SyncInfoValueTags.NEW_COOKIE_TAG.getValue() );
349
350                // As the OCTET_STRING is absorbed by the Application tag,
351                // we have to store the L and V separately
352                if ( ( getCookie() == null ) || ( getCookie().length == 0 ) )
353                {
354                    buffer.put( ( byte ) 0 );
355                }
356                else
357                {
358                    buffer.put( TLV.getBytes( getCookie().length ) );
359                    buffer.put( getCookie() );
360                }
361
362                break;
363
364            case REFRESH_DELETE:
365                // The second case : refreshDelete
366                buffer.put( ( byte ) SyncInfoValueTags.REFRESH_DELETE_TAG.getValue() );
367                buffer.put( TLV.getBytes( syncInfoValueLength ) );
368
369                // The cookie, if any
370                if ( getCookie() != null )
371                {
372                    BerValue.encode( buffer, getCookie() );
373                }
374
375                // The refreshDone flag
376                if ( !isRefreshDone() )
377                {
378                    BerValue.encode( buffer, isRefreshDone() );
379                }
380
381                break;
382
383            case REFRESH_PRESENT:
384                // The third case : refreshPresent
385                buffer.put( ( byte ) SyncInfoValueTags.REFRESH_PRESENT_TAG.getValue() );
386                buffer.put( TLV.getBytes( syncInfoValueLength ) );
387
388                // The cookie, if any
389                if ( getCookie() != null )
390                {
391                    BerValue.encode( buffer, getCookie() );
392                }
393
394                // The refreshDone flag
395                if ( !isRefreshDone() )
396                {
397                    BerValue.encode( buffer, isRefreshDone() );
398                }
399
400                break;
401
402            case SYNC_ID_SET:
403                // The last case : syncIdSet
404                buffer.put( ( byte ) SyncInfoValueTags.SYNC_ID_SET_TAG.getValue() );
405                buffer.put( TLV.getBytes( syncInfoValueLength ) );
406
407                // The cookie, if any
408                if ( getCookie() != null )
409                {
410                    BerValue.encode( buffer, getCookie() );
411                }
412
413                // The refreshDeletes flag if not false
414                if ( isRefreshDeletes() )
415                {
416                    BerValue.encode( buffer, isRefreshDeletes() );
417                }
418
419                // The syncUUIDs
420                buffer.put( UniversalTag.SET.getValue() );
421                buffer.put( TLV.getBytes( syncUUIDsLength ) );
422
423                // Loop on the UUIDs if any
424                if ( !getSyncUUIDs().isEmpty() )
425                {
426                    for ( byte[] syncUUID : getSyncUUIDs() )
427                    {
428                        BerValue.encode( buffer, syncUUID );
429                    }
430                }
431
432                break;
433
434            default:
435                throw new IllegalArgumentException( "Unexpected SynchronizationInfo: " + getType() );
436        }
437
438        return buffer;
439    }
440
441
442    /**
443     * {@inheritDoc}
444     */
445    @Override
446    public byte[] getValue()
447    {
448        if ( value == null )
449        {
450            try
451            {
452                computeLength();
453                ByteBuffer buffer = ByteBuffer.allocate( valueLength );
454
455                switch ( getType() )
456                {
457                    case NEW_COOKIE:
458                        // The first case : newCookie
459                        buffer.put( ( byte ) SyncInfoValueTags.NEW_COOKIE_TAG.getValue() );
460
461                        // As the OCTET_STRING is absorbed by the Application tag,
462                        // we have to store the L and V separately
463                        if ( ( getCookie() == null ) || ( getCookie().length == 0 ) )
464                        {
465                            buffer.put( ( byte ) 0 );
466                        }
467                        else
468                        {
469                            buffer.put( TLV.getBytes( getCookie().length ) );
470                            buffer.put( getCookie() );
471                        }
472
473                        break;
474
475                    case REFRESH_DELETE:
476                        // The second case : refreshDelete
477                        buffer.put( ( byte ) SyncInfoValueTags.REFRESH_DELETE_TAG.getValue() );
478                        buffer.put( TLV.getBytes( syncInfoValueLength ) );
479
480                        // The cookie, if any
481                        if ( getCookie() != null )
482                        {
483                            BerValue.encode( buffer, getCookie() );
484                        }
485
486                        // The refreshDone flag
487                        if ( !isRefreshDone() )
488                        {
489                            BerValue.encode( buffer, isRefreshDone() );
490                        }
491
492                        break;
493
494                    case REFRESH_PRESENT:
495                        // The third case : refreshPresent
496                        buffer.put( ( byte ) SyncInfoValueTags.REFRESH_PRESENT_TAG.getValue() );
497                        buffer.put( TLV.getBytes( syncInfoValueLength ) );
498
499                        // The cookie, if any
500                        if ( getCookie() != null )
501                        {
502                            BerValue.encode( buffer, getCookie() );
503                        }
504
505                        // The refreshDone flag
506                        if ( !isRefreshDone() )
507                        {
508                            BerValue.encode( buffer, isRefreshDone() );
509                        }
510
511                        break;
512
513                    case SYNC_ID_SET:
514                        // The last case : syncIdSet
515                        buffer.put( ( byte ) SyncInfoValueTags.SYNC_ID_SET_TAG.getValue() );
516                        buffer.put( TLV.getBytes( syncInfoValueLength ) );
517
518                        // The cookie, if any
519                        if ( getCookie() != null )
520                        {
521                            BerValue.encode( buffer, getCookie() );
522                        }
523
524                        // The refreshDeletes flag if not false
525                        if ( isRefreshDeletes() )
526                        {
527                            BerValue.encode( buffer, isRefreshDeletes() );
528                        }
529
530                        // The syncUUIDs
531                        buffer.put( UniversalTag.SET.getValue() );
532                        buffer.put( TLV.getBytes( syncUUIDsLength ) );
533
534                        // Loop on the UUIDs if any
535                        if ( !getSyncUUIDs().isEmpty() )
536                        {
537                            for ( byte[] syncUUID : getSyncUUIDs() )
538                            {
539                                BerValue.encode( buffer, syncUUID );
540                            }
541                        }
542
543                        break;
544
545                    default:
546                        throw new IllegalArgumentException( "Unexpected SynchronizationInfo: " + getType() );
547                }
548
549                value = buffer.array();
550            }
551            catch ( EncoderException e )
552            {
553                return null;
554            }
555        }
556
557        return value;
558    }
559
560
561    /**
562     * {@inheritDoc}
563     */
564    @Override
565    public Asn1Object decode( byte[] controlBytes ) throws DecoderException
566    {
567        ByteBuffer bb = ByteBuffer.wrap( controlBytes );
568        SyncInfoValueContainer container = new SyncInfoValueContainer( getCodecService(), this );
569        DECODER.decode( bb, container );
570        return this;
571    }
572
573
574    /**
575     * @see Object#toString()
576     */
577    @Override
578    public String toString()
579    {
580        return getDecorated().toString();
581    }
582}