001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 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.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. 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 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 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 debugException(ae); 261 throw new LDAPException(ResultCode.DECODING_ERROR, 262 ERR_ECN_CANNOT_DECODE_CHANGE_NUMBER.get(ae), 263 ae); 264 } 265 break; 266 267 default: 268 throw new LDAPException(ResultCode.DECODING_ERROR, 269 ERR_ECN_INVALID_ELEMENT_TYPE.get( 270 toHex(ecnElements[i].getType()))); 271 } 272 } 273 274 previousDN = prevDN; 275 changeNumber = chgNum; 276 } 277 278 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override() 284 public EntryChangeNotificationControl 285 decodeControl(final String oid, final boolean isCritical, 286 final ASN1OctetString value) 287 throws LDAPException 288 { 289 return new EntryChangeNotificationControl(oid, isCritical, value); 290 } 291 292 293 294 /** 295 * Extracts an entry change notification control from the provided search 296 * result entry. 297 * 298 * @param entry The search result entry from which to retrieve the entry 299 * change notification control. 300 * 301 * @return The entry change notification control contained in the provided 302 * search result entry, or {@code null} if the entry did not contain 303 * an entry change notification control. 304 * 305 * @throws LDAPException If a problem is encountered while attempting to 306 * decode the entry change notification control 307 * contained in the provided entry. 308 */ 309 public static EntryChangeNotificationControl 310 get(final SearchResultEntry entry) 311 throws LDAPException 312 { 313 final Control c = entry.getControl(ENTRY_CHANGE_NOTIFICATION_OID); 314 if (c == null) 315 { 316 return null; 317 } 318 319 if (c instanceof EntryChangeNotificationControl) 320 { 321 return (EntryChangeNotificationControl) c; 322 } 323 else 324 { 325 return new EntryChangeNotificationControl(c.getOID(), c.isCritical(), 326 c.getValue()); 327 } 328 } 329 330 331 332 /** 333 * Encodes the provided information into an octet string that can be used as 334 * the value for this control. 335 * 336 * @param changeType The change type for the change. It must not be 337 * {@code null}. 338 * @param previousDN The previous DN of the entry, if applicable. 339 * @param changeNumber The change number to include in this control, or 340 * -1 if there should not be a change number. 341 * 342 * @return An ASN.1 octet string that can be used as the value for this 343 * control. 344 */ 345 private static ASN1OctetString encodeValue( 346 final PersistentSearchChangeType changeType, 347 final String previousDN, final long changeNumber) 348 { 349 ensureNotNull(changeType); 350 351 final ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3); 352 elementList.add(new ASN1Enumerated(changeType.intValue())); 353 354 if (previousDN != null) 355 { 356 elementList.add(new ASN1OctetString(previousDN)); 357 } 358 359 if (changeNumber > 0) 360 { 361 elementList.add(new ASN1Long(changeNumber)); 362 } 363 364 return new ASN1OctetString(new ASN1Sequence(elementList).encode()); 365 } 366 367 368 369 /** 370 * Retrieves the change type for this entry change notification control. 371 * 372 * @return The change type for this entry change notification control. 373 */ 374 public PersistentSearchChangeType getChangeType() 375 { 376 return changeType; 377 } 378 379 380 381 /** 382 * Retrieves the previous DN for the entry, if applicable. 383 * 384 * @return The previous DN for the entry, or {@code null} if there is none. 385 */ 386 public String getPreviousDN() 387 { 388 return previousDN; 389 } 390 391 392 393 /** 394 * Retrieves the change number for the associated change, if available. 395 * 396 * @return The change number for the associated change, or -1 if none was 397 * provided. 398 */ 399 public long getChangeNumber() 400 { 401 return changeNumber; 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public String getControlName() 411 { 412 return INFO_CONTROL_NAME_ENTRY_CHANGE_NOTIFICATION.get(); 413 } 414 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override() 421 public void toString(final StringBuilder buffer) 422 { 423 buffer.append("EntryChangeNotificationControl(changeType="); 424 buffer.append(changeType.getName()); 425 426 if (previousDN != null) 427 { 428 buffer.append(", previousDN='"); 429 buffer.append(previousDN); 430 buffer.append('\''); 431 } 432 433 if (changeNumber > 0) 434 { 435 buffer.append(", changeNumber="); 436 buffer.append(changeNumber); 437 } 438 439 buffer.append(", isCritical="); 440 buffer.append(isCritical()); 441 buffer.append(')'); 442 } 443}