001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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 */
021 package com.unboundid.ldap.sdk.controls;
022
023
024
025 import java.util.ArrayList;
026
027 import com.unboundid.asn1.ASN1Constants;
028 import com.unboundid.asn1.ASN1Element;
029 import com.unboundid.asn1.ASN1Enumerated;
030 import com.unboundid.asn1.ASN1Exception;
031 import com.unboundid.asn1.ASN1Long;
032 import com.unboundid.asn1.ASN1OctetString;
033 import com.unboundid.asn1.ASN1Sequence;
034 import com.unboundid.ldap.sdk.Control;
035 import com.unboundid.ldap.sdk.DecodeableControl;
036 import com.unboundid.ldap.sdk.LDAPException;
037 import com.unboundid.ldap.sdk.ResultCode;
038 import com.unboundid.ldap.sdk.SearchResultEntry;
039 import com.unboundid.util.NotMutable;
040 import com.unboundid.util.ThreadSafety;
041 import com.unboundid.util.ThreadSafetyLevel;
042
043 import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
044 import static com.unboundid.util.Debug.*;
045 import static com.unboundid.util.StaticUtils.*;
046 import 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)
083 public 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 }