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 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 }