001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 Ping Identity Corporation 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.Debug; 040import com.unboundid.util.NotMutable; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044import com.unboundid.util.Validator; 045 046import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 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. Response controls should generally not be 157 * critical. 158 */ 159 public EntryChangeNotificationControl( 160 final PersistentSearchChangeType changeType, 161 final String previousDN, final long changeNumber, 162 final boolean isCritical) 163 { 164 super(ENTRY_CHANGE_NOTIFICATION_OID, isCritical, 165 encodeValue(changeType, previousDN, changeNumber)); 166 167 this.changeType = changeType; 168 this.previousDN = previousDN; 169 this.changeNumber = changeNumber; 170 } 171 172 173 174 /** 175 * Creates a new entry change notification control with the provided 176 * information. 177 * 178 * @param oid The OID for the control. 179 * @param isCritical Indicates whether the control should be marked 180 * critical. 181 * @param value The encoded value for the control. This may be 182 * {@code null} if no value was provided. 183 * 184 * @throws LDAPException If the provided control cannot be decoded as an 185 * entry change notification control. 186 */ 187 public EntryChangeNotificationControl(final String oid, 188 final boolean isCritical, 189 final ASN1OctetString value) 190 throws LDAPException 191 { 192 super(oid, isCritical, value); 193 194 if (value == null) 195 { 196 throw new LDAPException(ResultCode.DECODING_ERROR, 197 ERR_ECN_NO_VALUE.get()); 198 } 199 200 final ASN1Sequence ecnSequence; 201 try 202 { 203 final ASN1Element element = ASN1Element.decode(value.getValue()); 204 ecnSequence = ASN1Sequence.decodeAsSequence(element); 205 } 206 catch (final ASN1Exception ae) 207 { 208 Debug.debugException(ae); 209 throw new LDAPException(ResultCode.DECODING_ERROR, 210 ERR_ECN_VALUE_NOT_SEQUENCE.get(ae), ae); 211 } 212 213 final ASN1Element[] ecnElements = ecnSequence.elements(); 214 if ((ecnElements.length < 1) || (ecnElements.length > 3)) 215 { 216 throw new LDAPException(ResultCode.DECODING_ERROR, 217 ERR_ECN_INVALID_ELEMENT_COUNT.get( 218 ecnElements.length)); 219 } 220 221 final ASN1Enumerated ecnEnumerated; 222 try 223 { 224 ecnEnumerated = ASN1Enumerated.decodeAsEnumerated(ecnElements[0]); 225 } 226 catch (final ASN1Exception ae) 227 { 228 Debug.debugException(ae); 229 throw new LDAPException(ResultCode.DECODING_ERROR, 230 ERR_ECN_FIRST_NOT_ENUMERATED.get(ae), ae); 231 } 232 233 changeType = PersistentSearchChangeType.valueOf(ecnEnumerated.intValue()); 234 if (changeType == null) 235 { 236 throw new LDAPException(ResultCode.DECODING_ERROR, 237 ERR_ECN_INVALID_CHANGE_TYPE.get( 238 ecnEnumerated.intValue())); 239 } 240 241 242 String prevDN = null; 243 long chgNum = -1; 244 for (int i=1; i < ecnElements.length; i++) 245 { 246 switch (ecnElements[i].getType()) 247 { 248 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 249 prevDN = ASN1OctetString.decodeAsOctetString( 250 ecnElements[i]).stringValue(); 251 break; 252 253 case ASN1Constants.UNIVERSAL_INTEGER_TYPE: 254 try 255 { 256 chgNum = ASN1Long.decodeAsLong(ecnElements[i]).longValue(); 257 } 258 catch (final ASN1Exception ae) 259 { 260 Debug.debugException(ae); 261 throw new LDAPException(ResultCode.DECODING_ERROR, 262 ERR_ECN_CANNOT_DECODE_CHANGE_NUMBER.get(ae), ae); 263 } 264 break; 265 266 default: 267 throw new LDAPException(ResultCode.DECODING_ERROR, 268 ERR_ECN_INVALID_ELEMENT_TYPE.get( 269 StaticUtils.toHex(ecnElements[i].getType()))); 270 } 271 } 272 273 previousDN = prevDN; 274 changeNumber = chgNum; 275 } 276 277 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override() 283 public EntryChangeNotificationControl 284 decodeControl(final String oid, final boolean isCritical, 285 final ASN1OctetString value) 286 throws LDAPException 287 { 288 return new EntryChangeNotificationControl(oid, isCritical, value); 289 } 290 291 292 293 /** 294 * Extracts an entry change notification control from the provided search 295 * result entry. 296 * 297 * @param entry The search result entry from which to retrieve the entry 298 * change notification control. 299 * 300 * @return The entry change notification control contained in the provided 301 * search result entry, or {@code null} if the entry did not contain 302 * an entry change notification control. 303 * 304 * @throws LDAPException If a problem is encountered while attempting to 305 * decode the entry change notification control 306 * contained in the provided entry. 307 */ 308 public static EntryChangeNotificationControl 309 get(final SearchResultEntry entry) 310 throws LDAPException 311 { 312 final Control c = entry.getControl(ENTRY_CHANGE_NOTIFICATION_OID); 313 if (c == null) 314 { 315 return null; 316 } 317 318 if (c instanceof EntryChangeNotificationControl) 319 { 320 return (EntryChangeNotificationControl) c; 321 } 322 else 323 { 324 return new EntryChangeNotificationControl(c.getOID(), c.isCritical(), 325 c.getValue()); 326 } 327 } 328 329 330 331 /** 332 * Encodes the provided information into an octet string that can be used as 333 * the value for this control. 334 * 335 * @param changeType The change type for the change. It must not be 336 * {@code null}. 337 * @param previousDN The previous DN of the entry, if applicable. 338 * @param changeNumber The change number to include in this control, or 339 * -1 if there should not be a change number. 340 * 341 * @return An ASN.1 octet string that can be used as the value for this 342 * control. 343 */ 344 private static ASN1OctetString encodeValue( 345 final PersistentSearchChangeType changeType, 346 final String previousDN, final long changeNumber) 347 { 348 Validator.ensureNotNull(changeType); 349 350 final ArrayList<ASN1Element> elementList = new ArrayList<>(3); 351 elementList.add(new ASN1Enumerated(changeType.intValue())); 352 353 if (previousDN != null) 354 { 355 elementList.add(new ASN1OctetString(previousDN)); 356 } 357 358 if (changeNumber > 0) 359 { 360 elementList.add(new ASN1Long(changeNumber)); 361 } 362 363 return new ASN1OctetString(new ASN1Sequence(elementList).encode()); 364 } 365 366 367 368 /** 369 * Retrieves the change type for this entry change notification control. 370 * 371 * @return The change type for this entry change notification control. 372 */ 373 public PersistentSearchChangeType getChangeType() 374 { 375 return changeType; 376 } 377 378 379 380 /** 381 * Retrieves the previous DN for the entry, if applicable. 382 * 383 * @return The previous DN for the entry, or {@code null} if there is none. 384 */ 385 public String getPreviousDN() 386 { 387 return previousDN; 388 } 389 390 391 392 /** 393 * Retrieves the change number for the associated change, if available. 394 * 395 * @return The change number for the associated change, or -1 if none was 396 * provided. 397 */ 398 public long getChangeNumber() 399 { 400 return changeNumber; 401 } 402 403 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override() 409 public String getControlName() 410 { 411 return INFO_CONTROL_NAME_ENTRY_CHANGE_NOTIFICATION.get(); 412 } 413 414 415 416 /** 417 * {@inheritDoc} 418 */ 419 @Override() 420 public void toString(final StringBuilder buffer) 421 { 422 buffer.append("EntryChangeNotificationControl(changeType="); 423 buffer.append(changeType.getName()); 424 425 if (previousDN != null) 426 { 427 buffer.append(", previousDN='"); 428 buffer.append(previousDN); 429 buffer.append('\''); 430 } 431 432 if (changeNumber > 0) 433 { 434 buffer.append(", changeNumber="); 435 buffer.append(changeNumber); 436 } 437 438 buffer.append(", isCritical="); 439 buffer.append(isCritical()); 440 buffer.append(')'); 441 } 442}