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 "2.16.840.1.113730.3.4.7". 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 01:05:29 +0200 (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 }