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.controls.entryChange;
021    
022    
023    import java.nio.ByteBuffer;
024    
025    import org.apache.directory.shared.asn1.Asn1Object;
026    import org.apache.directory.shared.asn1.ber.tlv.TLV;
027    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
028    import org.apache.directory.shared.asn1.ber.tlv.Value;
029    import org.apache.directory.shared.asn1.codec.EncoderException;
030    import org.apache.directory.shared.i18n.I18n;
031    import org.apache.directory.shared.ldap.codec.controls.AbstractControl;
032    import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
033    import org.apache.directory.shared.ldap.name.DN;
034    import org.apache.directory.shared.ldap.util.StringTools;
035    
036    
037    /**
038     * A response control that may be returned by Persistent Search entry responses.
039     * It contains addition change information to describe the exact change that
040     * occurred to an entry. The exact details of this control are covered in section
041     * 5 of this (yes) expired draft: <a
042     * href="http://www3.ietf.org/proceedings/01aug/I-D/draft-ietf-ldapext-psearch-03.txt">
043     * Persistent Search Draft v03</a> which is printed out below for convenience:
044     * 
045     * <pre>
046     *    5.  Entry Change Notification Control
047     *    
048     *    This control provides additional information about the change the caused
049     *    a particular entry to be returned as the result of a persistent search.
050     *    The controlType is &quot;2.16.840.1.113730.3.4.7&quot;.  If the client set the
051     *    returnECs boolean to TRUE in the PersistentSearch control, servers MUST
052     *    include an EntryChangeNotification control in the Controls portion of
053     *    each SearchResultEntry that is returned due to an entry being added,
054     *    deleted, or modified.
055     *    
056     *               EntryChangeNotification ::= SEQUENCE 
057     *               {
058     *                         changeType ENUMERATED 
059     *                         {
060     *                                 add             (1),
061     *                                 delete          (2),
062     *                                 modify          (4),
063     *                                 modDN           (8)
064     *                         },
065     *                         previousDN   LDAPDN OPTIONAL,     -- modifyDN ops. only
066     *                         changeNumber INTEGER OPTIONAL     -- if supported
067     *               }
068     *    
069     *    changeType indicates what LDAP operation caused the entry to be
070     *    returned.
071     *    
072     *    previousDN is present only for modifyDN operations and gives the DN of
073     *    the entry before it was renamed and/or moved.  Servers MUST include this
074     *    optional field only when returning change notifications as a result of
075     *    modifyDN operations.
076     * 
077     *    changeNumber is the change number [CHANGELOG] assigned by a server for
078     *    the change.  If a server supports an LDAP Change Log it SHOULD include
079     *    this field.
080     * </pre>
081     * 
082     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
083     * @version $Rev: 918756 $, $Date: 2010-03-04 00:05:29 +0100 (Thu, 04 Mar 2010) $, 
084     */
085    public class EntryChangeControl extends AbstractControl
086    {
087        /** The EntryChange control */
088        public static final String CONTROL_OID = "2.16.840.1.113730.3.4.7";
089    
090        public static final int UNDEFINED_CHANGE_NUMBER = -1;
091    
092        private ChangeType changeType = ChangeType.ADD;
093    
094        private long changeNumber = UNDEFINED_CHANGE_NUMBER;
095    
096        /** The previous DN */
097        private DN previousDn = null;
098        
099        /** A temporary storage for the previous DN */
100        private byte[] previousDnBytes = null;
101    
102        /** The entry change global length */
103        private int eccSeqLength;
104    
105    
106        /**
107         * @see Asn1Object#Asn1Object
108         */
109        public EntryChangeControl()
110        {
111            super( CONTROL_OID );
112            
113            decoder = new EntryChangeControlDecoder();
114        }
115    
116        
117        /**
118         * Compute the EntryChangeControl length 
119         * 
120         * 0x30 L1 
121         *   | 
122         *   +--> 0x0A 0x0(1-4) [1|2|4|8] (changeType) 
123         *  [+--> 0x04 L2 previousDN] 
124         *  [+--> 0x02 0x0(1-4) [0..2^63-1] (changeNumber)]
125         */
126        public int computeLength()
127        {
128            int changeTypesLength = 1 + 1 + 1;
129    
130            int previousDnLength = 0;
131            int changeNumberLength = 0;
132    
133            if ( previousDn != null )
134            {
135                previousDnBytes = StringTools.getBytesUtf8( previousDn.getName() );
136                previousDnLength = 1 + TLV.getNbBytes( previousDnBytes.length ) + previousDnBytes.length;
137            }
138    
139            if ( changeNumber != UNDEFINED_CHANGE_NUMBER )
140            {
141                changeNumberLength = 1 + 1 + Value.getNbBytes( changeNumber );
142            }
143    
144            eccSeqLength = changeTypesLength + previousDnLength + changeNumberLength;
145            valueLength = 1 + TLV.getNbBytes( eccSeqLength ) + eccSeqLength;
146    
147            // Call the super class to compute the global control length
148            return super.computeLength( valueLength );
149        }
150    
151    
152        /**
153         * Encodes the entry change control.
154         * 
155         * @param buffer The encoded sink
156         * @return A ByteBuffer that contains the encoded PDU
157         * @throws EncoderException If anything goes wrong.
158         */
159        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
160        {
161            if ( buffer == null )
162            {
163                throw new EncoderException( I18n.err( I18n.ERR_04023 ) );
164            }
165    
166            // Encode the Control envelop
167            super.encode( buffer );
168            
169            // Encode the OCTET_STRING tag
170            buffer.put( UniversalTag.OCTET_STRING_TAG );
171            buffer.put( TLV.getBytes( valueLength ) );
172    
173            buffer.put( UniversalTag.SEQUENCE_TAG );
174            buffer.put( TLV.getBytes( eccSeqLength ) );
175    
176            buffer.put( UniversalTag.ENUMERATED_TAG );
177            buffer.put( ( byte ) 1 );
178            buffer.put( Value.getBytes( changeType.getValue() ) );
179    
180            if ( previousDn != null )
181            {
182                Value.encode( buffer, previousDnBytes );
183            }
184            
185            if ( changeNumber != UNDEFINED_CHANGE_NUMBER )
186            {
187                Value.encode( buffer, changeNumber );
188            }
189            
190            return buffer;
191        }
192        
193        
194        /**
195         * {@inheritDoc}
196         */
197        public byte[] getValue()
198        {
199            if ( value == null )
200            {
201                try
202                { 
203                    computeLength();
204                    ByteBuffer buffer = ByteBuffer.allocate( valueLength );
205                    
206                    buffer.put( UniversalTag.SEQUENCE_TAG );
207                    buffer.put( TLV.getBytes( eccSeqLength ) );
208            
209                    buffer.put( UniversalTag.ENUMERATED_TAG );
210                    buffer.put( ( byte ) 1 );
211                    buffer.put( Value.getBytes( changeType.getValue() ) );
212            
213                    if ( previousDn != null )
214                    {
215                        Value.encode( buffer, previousDnBytes );
216                    }
217                    
218                    if ( changeNumber != UNDEFINED_CHANGE_NUMBER )
219                    {
220                        Value.encode( buffer, changeNumber );
221                    }
222                    
223                    value = buffer.array();
224                }
225                catch ( Exception e )
226                {
227                    return null;
228                }
229            }
230            
231            return value;
232        }
233    
234    
235        /**
236         * @return The ChangeType
237         */
238        public ChangeType getChangeType()
239        {
240            return changeType;
241        }
242    
243    
244        /**
245         * Set the ChangeType
246         *
247         * @param changeType Add, Delete; Modify or ModifyDN
248         */
249        public void setChangeType( ChangeType changeType )
250        {
251            this.changeType = changeType;
252        }
253    
254    
255        public DN getPreviousDn()
256        {
257            return previousDn;
258        }
259    
260    
261        public void setPreviousDn( DN previousDn )
262        {
263            this.previousDn = previousDn;
264        }
265    
266    
267        public long getChangeNumber()
268        {
269            return changeNumber;
270        }
271    
272    
273        public void setChangeNumber( long changeNumber )
274        {
275            this.changeNumber = changeNumber;
276        }
277    
278    
279        /**
280         * Return a String representing this EntryChangeControl.
281         */
282        public String toString()
283        {
284            StringBuffer sb = new StringBuffer();
285    
286            sb.append( "    Entry Change Control\n" );
287            sb.append( "        oid : " ).append( getOid() ).append( '\n' );
288            sb.append( "        critical : " ).append( isCritical() ).append( '\n' );
289            sb.append( "        changeType   : '" ).append( changeType ).append( "'\n" );
290            sb.append( "        previousDN   : '" ).append( previousDn ).append( "'\n" );
291            
292            if ( changeNumber == UNDEFINED_CHANGE_NUMBER )
293            {
294                sb.append( "        changeNumber : '" ).append( "UNDEFINED" ).append( "'\n" );
295            }
296            else
297            {
298                sb.append( "        changeNumber : '" ).append( changeNumber ).append( "'\n" );
299            }
300            
301            return sb.toString();
302        }
303    }