001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.controls; 022 023 024 025import java.util.ArrayList; 026 027import com.unboundid.asn1.ASN1Constants; 028import com.unboundid.asn1.ASN1Element; 029import com.unboundid.asn1.ASN1Enumerated; 030import com.unboundid.asn1.ASN1Exception; 031import com.unboundid.asn1.ASN1Long; 032import com.unboundid.asn1.ASN1OctetString; 033import com.unboundid.asn1.ASN1Sequence; 034import com.unboundid.ldap.sdk.Control; 035import com.unboundid.ldap.sdk.DecodeableControl; 036import com.unboundid.ldap.sdk.LDAPException; 037import com.unboundid.ldap.sdk.ResultCode; 038import com.unboundid.ldap.sdk.SearchResultEntry; 039import com.unboundid.util.NotMutable; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 044import static com.unboundid.util.Debug.*; 045import static com.unboundid.util.StaticUtils.*; 046import static com.unboundid.util.Validator.*; 047 048 049 050/** 051 * This class provides an implementation of the entry change notification 052 * control as defined in draft-ietf-ldapext-psearch. It will be returned in 053 * search result entries that match the criteria associated with a persistent 054 * search (see the {@link PersistentSearchRequestControl} class) and have been 055 * changed in a way associated with the registered change types for that search. 056 * <BR><BR> 057 * The information that can be included in an entry change notification control 058 * includes: 059 * <UL> 060 * <LI>A change type, which indicates the type of operation that was performed 061 * to trigger this entry change notification control. It will be one of 062 * the values of the {@link PersistentSearchChangeType} enum.</LI> 063 * <LI>An optional previous DN, which indicates the DN that the entry had 064 * before the associated operation was processed. It will only be present 065 * if the associated operation was a modify DN operation.</LI> 066 * <LI>An optional change number, which may be used to retrieve additional 067 * information about the associated operation from the server. This may 068 * not be available in all directory server implementations.</LI> 069 * </UL> 070 * Note that the entry change notification control should only be included in 071 * search result entries that are associated with a search request that included 072 * the persistent search request control, and only if that persistent search 073 * request control had the {@code returnECs} flag set to {@code true} to 074 * indicate that entry change notification controls should be included in 075 * resulting entries. Further, the entry change notification control will only 076 * be included in entries that are returned as the result of a change in the 077 * server and not any of the preliminary entries that may be returned if the 078 * corresponding persistent search request had the {@code changesOnly} flag set 079 * to {@code false}. 080 */ 081@NotMutable() 082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 083public final class EntryChangeNotificationControl 084 extends Control 085 implements DecodeableControl 086{ 087 /** 088 * The OID (2.16.840.1.113730.3.4.7) for the entry change notification 089 * control. 090 */ 091 public static final String ENTRY_CHANGE_NOTIFICATION_OID = 092 "2.16.840.1.113730.3.4.7"; 093 094 095 096 /** 097 * The serial version UID for this serializable class. 098 */ 099 private static final long serialVersionUID = -1305357948140939303L; 100 101 102 103 // The change number for the change, if available. 104 private final long changeNumber; 105 106 // The change type for the change. 107 private final PersistentSearchChangeType changeType; 108 109 // The previous DN of the entry, if applicable. 110 private final String previousDN; 111 112 113 114 /** 115 * Creates a new empty control instance that is intended to be used only for 116 * decoding controls via the {@code DecodeableControl} interface. 117 */ 118 EntryChangeNotificationControl() 119 { 120 changeNumber = -1; 121 changeType = null; 122 previousDN = null; 123 } 124 125 126 127 /** 128 * Creates a new entry change notification control with the provided 129 * information. It will not be critical. 130 * 131 * @param changeType The change type for the change. It must not be 132 * {@code null}. 133 * @param previousDN The previous DN of the entry, if applicable. 134 * @param changeNumber The change number to include in this control, or 135 * -1 if there should not be a change number. 136 */ 137 public EntryChangeNotificationControl( 138 final PersistentSearchChangeType changeType, 139 final String previousDN, final long changeNumber) 140 { 141 this(changeType, previousDN, changeNumber, false); 142 } 143 144 145 146 /** 147 * Creates a new entry change notification control with the provided 148 * information. 149 * 150 * @param changeType The change type for the change. It must not be 151 * {@code null}. 152 * @param previousDN The previous DN of the entry, if applicable. 153 * @param changeNumber The change number to include in this control, or 154 * -1 if there should not be a change number. 155 * @param isCritical Indicates whether this control should be marked 156 * critical. 157 */ 158 public EntryChangeNotificationControl( 159 final PersistentSearchChangeType changeType, 160 final String previousDN, final long changeNumber, 161 final boolean isCritical) 162 { 163 super(ENTRY_CHANGE_NOTIFICATION_OID, isCritical, 164 encodeValue(changeType, previousDN, changeNumber)); 165 166 this.changeType = changeType; 167 this.previousDN = previousDN; 168 this.changeNumber = changeNumber; 169 } 170 171 172 173 /** 174 * Creates a new entry change notification control with the provided 175 * information. 176 * 177 * @param oid The OID for the control. 178 * @param isCritical Indicates whether the control should be marked 179 * critical. 180 * @param value The encoded value for the control. This may be 181 * {@code null} if no value was provided. 182 * 183 * @throws LDAPException If the provided control cannot be decoded as an 184 * entry change notification control. 185 */ 186 public EntryChangeNotificationControl(final String oid, 187 final boolean isCritical, 188 final ASN1OctetString value) 189 throws LDAPException 190 { 191 super(oid, isCritical, value); 192 193 if (value == null) 194 { 195 throw new LDAPException(ResultCode.DECODING_ERROR, 196 ERR_ECN_NO_VALUE.get()); 197 } 198 199 final ASN1Sequence ecnSequence; 200 try 201 { 202 final ASN1Element element = ASN1Element.decode(value.getValue()); 203 ecnSequence = ASN1Sequence.decodeAsSequence(element); 204 } 205 catch (final ASN1Exception ae) 206 { 207 debugException(ae); 208 throw new LDAPException(ResultCode.DECODING_ERROR, 209 ERR_ECN_VALUE_NOT_SEQUENCE.get(ae), ae); 210 } 211 212 final ASN1Element[] ecnElements = ecnSequence.elements(); 213 if ((ecnElements.length < 1) || (ecnElements.length > 3)) 214 { 215 throw new LDAPException(ResultCode.DECODING_ERROR, 216 ERR_ECN_INVALID_ELEMENT_COUNT.get( 217 ecnElements.length)); 218 } 219 220 final ASN1Enumerated ecnEnumerated; 221 try 222 { 223 ecnEnumerated = ASN1Enumerated.decodeAsEnumerated(ecnElements[0]); 224 } 225 catch (final ASN1Exception ae) 226 { 227 debugException(ae); 228 throw new LDAPException(ResultCode.DECODING_ERROR, 229 ERR_ECN_FIRST_NOT_ENUMERATED.get(ae), ae); 230 } 231 232 changeType = PersistentSearchChangeType.valueOf(ecnEnumerated.intValue()); 233 if (changeType == null) 234 { 235 throw new LDAPException(ResultCode.DECODING_ERROR, 236 ERR_ECN_INVALID_CHANGE_TYPE.get( 237 ecnEnumerated.intValue())); 238 } 239 240 241 String prevDN = null; 242 long chgNum = -1; 243 for (int i=1; i < ecnElements.length; i++) 244 { 245 switch (ecnElements[i].getType()) 246 { 247 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 248 prevDN = ASN1OctetString.decodeAsOctetString( 249 ecnElements[i]).stringValue(); 250 break; 251 252 case ASN1Constants.UNIVERSAL_INTEGER_TYPE: 253 try 254 { 255 chgNum = ASN1Long.decodeAsLong(ecnElements[i]).longValue(); 256 } 257 catch (final ASN1Exception ae) 258 { 259 debugException(ae); 260 throw new LDAPException(ResultCode.DECODING_ERROR, 261 ERR_ECN_CANNOT_DECODE_CHANGE_NUMBER.get(ae), 262 ae); 263 } 264 break; 265 266 default: 267 throw new LDAPException(ResultCode.DECODING_ERROR, 268 ERR_ECN_INVALID_ELEMENT_TYPE.get( 269 toHex(ecnElements[i].getType()))); 270 } 271 } 272 273 previousDN = prevDN; 274 changeNumber = chgNum; 275 } 276 277 278 279 /** 280 * {@inheritDoc} 281 */ 282 public EntryChangeNotificationControl 283 decodeControl(final String oid, final boolean isCritical, 284 final ASN1OctetString value) 285 throws LDAPException 286 { 287 return new EntryChangeNotificationControl(oid, isCritical, value); 288 } 289 290 291 292 /** 293 * Extracts an entry change notification control from the provided search 294 * result entry. 295 * 296 * @param entry The search result entry from which to retrieve the entry 297 * change notification control. 298 * 299 * @return The entry change notification control contained in the provided 300 * search result entry, or {@code null} if the entry did not contain 301 * an entry change notification control. 302 * 303 * @throws LDAPException If a problem is encountered while attempting to 304 * decode the entry change notification control 305 * contained in the provided entry. 306 */ 307 public static EntryChangeNotificationControl 308 get(final SearchResultEntry entry) 309 throws LDAPException 310 { 311 final Control c = entry.getControl(ENTRY_CHANGE_NOTIFICATION_OID); 312 if (c == null) 313 { 314 return null; 315 } 316 317 if (c instanceof EntryChangeNotificationControl) 318 { 319 return (EntryChangeNotificationControl) c; 320 } 321 else 322 { 323 return new EntryChangeNotificationControl(c.getOID(), c.isCritical(), 324 c.getValue()); 325 } 326 } 327 328 329 330 /** 331 * Encodes the provided information into an octet string that can be used as 332 * the value for this control. 333 * 334 * @param changeType The change type for the change. It must not be 335 * {@code null}. 336 * @param previousDN The previous DN of the entry, if applicable. 337 * @param changeNumber The change number to include in this control, or 338 * -1 if there should not be a change number. 339 * 340 * @return An ASN.1 octet string that can be used as the value for this 341 * control. 342 */ 343 private static ASN1OctetString encodeValue( 344 final PersistentSearchChangeType changeType, 345 final String previousDN, final long changeNumber) 346 { 347 ensureNotNull(changeType); 348 349 final ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3); 350 elementList.add(new ASN1Enumerated(changeType.intValue())); 351 352 if (previousDN != null) 353 { 354 elementList.add(new ASN1OctetString(previousDN)); 355 } 356 357 if (changeNumber > 0) 358 { 359 elementList.add(new ASN1Long(changeNumber)); 360 } 361 362 return new ASN1OctetString(new ASN1Sequence(elementList).encode()); 363 } 364 365 366 367 /** 368 * Retrieves the change type for this entry change notification control. 369 * 370 * @return The change type for this entry change notification control. 371 */ 372 public PersistentSearchChangeType getChangeType() 373 { 374 return changeType; 375 } 376 377 378 379 /** 380 * Retrieves the previous DN for the entry, if applicable. 381 * 382 * @return The previous DN for the entry, or {@code null} if there is none. 383 */ 384 public String getPreviousDN() 385 { 386 return previousDN; 387 } 388 389 390 391 /** 392 * Retrieves the change number for the associated change, if available. 393 * 394 * @return The change number for the associated change, or -1 if none was 395 * provided. 396 */ 397 public long getChangeNumber() 398 { 399 return changeNumber; 400 } 401 402 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override() 408 public String getControlName() 409 { 410 return INFO_CONTROL_NAME_ENTRY_CHANGE_NOTIFICATION.get(); 411 } 412 413 414 415 /** 416 * {@inheritDoc} 417 */ 418 @Override() 419 public void toString(final StringBuilder buffer) 420 { 421 buffer.append("EntryChangeNotificationControl(changeType="); 422 buffer.append(changeType.getName()); 423 424 if (previousDN != null) 425 { 426 buffer.append(", previousDN='"); 427 buffer.append(previousDN); 428 buffer.append('\''); 429 } 430 431 if (changeNumber > 0) 432 { 433 buffer.append(", changeNumber="); 434 buffer.append(changeNumber); 435 } 436 437 buffer.append(", isCritical="); 438 buffer.append(isCritical()); 439 buffer.append(')'); 440 } 441}