001 /*
002 * Copyright 2010-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2010-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.text.ParseException;
026 import java.util.ArrayList;
027 import java.util.Collections;
028 import java.util.Iterator;
029 import java.util.List;
030 import java.util.UUID;
031
032 import com.unboundid.asn1.ASN1Boolean;
033 import com.unboundid.asn1.ASN1Constants;
034 import com.unboundid.asn1.ASN1Element;
035 import com.unboundid.asn1.ASN1OctetString;
036 import com.unboundid.asn1.ASN1Sequence;
037 import com.unboundid.asn1.ASN1Set;
038 import com.unboundid.ldap.sdk.Control;
039 import com.unboundid.ldap.sdk.IntermediateResponse;
040 import com.unboundid.ldap.sdk.LDAPException;
041 import com.unboundid.ldap.sdk.ResultCode;
042 import com.unboundid.util.Debug;
043 import com.unboundid.util.NotMutable;
044 import com.unboundid.util.StaticUtils;
045 import com.unboundid.util.ThreadSafety;
046 import com.unboundid.util.ThreadSafetyLevel;
047 import com.unboundid.util.Validator;
048
049 import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
050
051
052
053 /**
054 * This class provides an implementation of the sync info message, which is
055 * an intermediate response message used by the content synchronization
056 * operation as defined in
057 * <a href="http://www.ietf.org/rfc/rfc4533.txt">RFC 4533</a>. Directory
058 * servers may return this response in the course of processing a search
059 * request containing the content synchronization request control. See the
060 * documentation for the {@link ContentSyncRequestControl} class for more
061 * information about using the content synchronization operation.
062 */
063 @NotMutable()
064 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065 public final class ContentSyncInfoIntermediateResponse
066 extends IntermediateResponse
067 {
068 /**
069 * The OID (1.3.6.1.4.1.4203.1.9.1.4) for the sync info intermediate response.
070 */
071 public static final String SYNC_INFO_OID = "1.3.6.1.4.1.4203.1.9.1.4";
072
073
074
075 /**
076 * The serial version UID for this serializable class.
077 */
078 private static final long serialVersionUID = 4464376009337157433L;
079
080
081
082 // An updated state cookie, if available.
083 private final ASN1OctetString cookie;
084
085 // Indicates whether the provided set of UUIDs represent entries that have
086 // been removed.
087 private final boolean refreshDeletes;
088
089 // Indicates whether the refresh phase is complete.
090 private final boolean refreshDone;
091
092 // The type of content synchronization information represented in this
093 // response.
094 private final ContentSyncInfoType type;
095
096 // A list of entryUUIDs for the set of entries associated with this message.
097 private final List<UUID> entryUUIDs;
098
099
100
101 /**
102 * Creates a new content synchronization info intermediate response with the
103 * provided information.
104 *
105 * @param type The type of content synchronization information
106 * represented in this response.
107 * @param value The encoded value for the intermediate response, if
108 * any.
109 * @param cookie An updated state cookie for the synchronization
110 * session, if available.
111 * @param refreshDone Indicates whether the refresh phase of the
112 * synchronization session is complete.
113 * @param refreshDeletes Indicates whether the provided set of UUIDs
114 * represent entries that have been removed.
115 * @param entryUUIDs A list of entryUUIDs for the set of entries
116 * associated with this message.
117 * @param controls The set of controls to include in the intermediate
118 * response, if any.
119 */
120 private ContentSyncInfoIntermediateResponse(final ContentSyncInfoType type,
121 final ASN1OctetString value, final ASN1OctetString cookie,
122 final boolean refreshDone, final boolean refreshDeletes,
123 final List<UUID> entryUUIDs, final Control... controls)
124 {
125 super(SYNC_INFO_OID, value, controls);
126
127 this.type = type;
128 this.cookie = cookie;
129 this.refreshDone = refreshDone;
130 this.refreshDeletes = refreshDeletes;
131 this.entryUUIDs = entryUUIDs;
132 }
133
134
135
136 /**
137 * Creates a new sync info intermediate response with a type of
138 * {@link ContentSyncInfoType#NEW_COOKIE}.
139 *
140 * @param cookie The updated state cookie for the synchronization session.
141 * It must not be {@code null}.
142 * @param controls An optional set of controls to include in the response.
143 * It may be {@code null} or empty if no controls should be
144 * included.
145 *
146 * @return The created sync info intermediate response.
147 */
148 public static ContentSyncInfoIntermediateResponse createNewCookieResponse(
149 final ASN1OctetString cookie, final Control... controls)
150 {
151 Validator.ensureNotNull(cookie);
152
153 final ContentSyncInfoType type = ContentSyncInfoType.NEW_COOKIE;
154
155 return new ContentSyncInfoIntermediateResponse(type,
156 encodeValue(type, cookie, false, null, false),
157 cookie, false, false, null, controls);
158 }
159
160
161
162 /**
163 * Creates a new sync info intermediate response with a type of
164 * {@link ContentSyncInfoType#REFRESH_DELETE}.
165 *
166 * @param cookie The updated state cookie for the synchronization
167 * session. It may be {@code null} if no new cookie is
168 * available.
169 * @param refreshDone Indicates whether the refresh phase of the
170 * synchronization operation has completed.
171 * @param controls An optional set of controls to include in the
172 * response. It may be {@code null} or empty if no
173 * controls should be included.
174 *
175 * @return The created sync info intermediate response.
176 */
177 public static ContentSyncInfoIntermediateResponse createRefreshDeleteResponse(
178 final ASN1OctetString cookie, final boolean refreshDone,
179 final Control... controls)
180 {
181 final ContentSyncInfoType type = ContentSyncInfoType.REFRESH_DELETE;
182
183 return new ContentSyncInfoIntermediateResponse(type,
184 encodeValue(type, cookie, refreshDone, null, false),
185 cookie, refreshDone, false, null, controls);
186 }
187
188
189
190 /**
191 * Creates a new sync info intermediate response with a type of
192 * {@link ContentSyncInfoType#REFRESH_PRESENT}.
193 *
194 * @param cookie The updated state cookie for the synchronization
195 * session. It may be {@code null} if no new cookie is
196 * available.
197 * @param refreshDone Indicates whether the refresh phase of the
198 * synchronization operation has completed.
199 * @param controls An optional set of controls to include in the
200 * response. It may be {@code null} or empty if no
201 * controls should be included.
202 *
203 * @return The created sync info intermediate response.
204 */
205 public static ContentSyncInfoIntermediateResponse
206 createRefreshPresentResponse(final ASN1OctetString cookie,
207 final boolean refreshDone,
208 final Control... controls)
209 {
210 final ContentSyncInfoType type = ContentSyncInfoType.REFRESH_PRESENT;
211
212 return new ContentSyncInfoIntermediateResponse(type,
213 encodeValue(type, cookie, refreshDone, null, false),
214 cookie, refreshDone, false, null, controls);
215 }
216
217
218
219 /**
220 * Creates a new sync info intermediate response with a type of
221 * {@link ContentSyncInfoType#SYNC_ID_SET}.
222 *
223 * @param cookie The updated state cookie for the synchronization
224 * session. It may be {@code null} if no new cookie
225 * is available.
226 * @param entryUUIDs The set of entryUUIDs for the entries referenced in
227 * this response. It must not be {@code null}.
228 * @param refreshDeletes Indicates whether the entryUUIDs represent entries
229 * that have been removed rather than those that have
230 * remained unchanged.
231 * @param controls An optional set of controls to include in the
232 * response. It may be {@code null} or empty if no
233 * controls should be included.
234 *
235 * @return The created sync info intermediate response.
236 */
237 public static ContentSyncInfoIntermediateResponse createSyncIDSetResponse(
238 final ASN1OctetString cookie, final List<UUID> entryUUIDs,
239 final boolean refreshDeletes, final Control... controls)
240 {
241 Validator.ensureNotNull(entryUUIDs);
242
243 final ContentSyncInfoType type = ContentSyncInfoType.SYNC_ID_SET;
244
245 return new ContentSyncInfoIntermediateResponse(type,
246 encodeValue(type, cookie, false, entryUUIDs, refreshDeletes),
247 cookie, false, refreshDeletes,
248 Collections.unmodifiableList(entryUUIDs), controls);
249 }
250
251
252
253 /**
254 * Decodes the provided generic intermediate response as a sync info
255 * intermediate response.
256 *
257 * @param r The intermediate response to be decoded as a sync info
258 * intermediate response. It must not be {@code null}.
259 *
260 * @return The decoded sync info intermediate response.
261 *
262 * @throws LDAPException If a problem occurs while trying to decode the
263 * provided intermediate response as a sync info
264 * response.
265 */
266 public static ContentSyncInfoIntermediateResponse decode(
267 final IntermediateResponse r)
268 throws LDAPException
269 {
270 final ASN1OctetString value = r.getValue();
271 if (value == null)
272 {
273 throw new LDAPException(ResultCode.DECODING_ERROR,
274 ERR_SYNC_INFO_IR_NO_VALUE.get());
275 }
276
277 final ASN1Element valueElement;
278 try
279 {
280 valueElement = ASN1Element.decode(value.getValue());
281 }
282 catch (final Exception e)
283 {
284 Debug.debugException(e);
285
286 throw new LDAPException(ResultCode.DECODING_ERROR,
287 ERR_SYNC_INFO_IR_VALUE_NOT_ELEMENT.get(
288 StaticUtils.getExceptionMessage(e)), e);
289 }
290
291 final ContentSyncInfoType type =
292 ContentSyncInfoType.valueOf(valueElement.getType());
293 if (type == null)
294 {
295 throw new LDAPException(ResultCode.DECODING_ERROR,
296 ERR_SYNC_INFO_IR_VALUE_UNRECOGNIZED_TYPE.get(
297 StaticUtils.toHex(valueElement.getType())));
298 }
299
300 ASN1OctetString cookie = null;
301 boolean refreshDone = false;
302 boolean refreshDeletes = false;
303 List<UUID> entryUUIDs = null;
304
305 try
306 {
307 switch (type)
308 {
309 case NEW_COOKIE:
310 cookie = new ASN1OctetString(valueElement.getValue());
311 break;
312
313 case REFRESH_DELETE:
314 case REFRESH_PRESENT:
315 refreshDone = true;
316
317 ASN1Sequence s = valueElement.decodeAsSequence();
318 for (final ASN1Element e : s.elements())
319 {
320 switch (e.getType())
321 {
322 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
323 cookie = ASN1OctetString.decodeAsOctetString(e);
324 break;
325 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
326 refreshDone = ASN1Boolean.decodeAsBoolean(e).booleanValue();
327 break;
328 default:
329 throw new LDAPException(ResultCode.DECODING_ERROR,
330 ERR_SYNC_INFO_IR_VALUE_INVALID_SEQUENCE_TYPE.get(
331 type.name(), StaticUtils.toHex(e.getType())));
332 }
333 }
334 break;
335
336 case SYNC_ID_SET:
337 s = valueElement.decodeAsSequence();
338 for (final ASN1Element e : s.elements())
339 {
340 switch (e.getType())
341 {
342 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
343 cookie = ASN1OctetString.decodeAsOctetString(e);
344 break;
345 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
346 refreshDeletes = ASN1Boolean.decodeAsBoolean(e).booleanValue();
347 break;
348 case ASN1Constants.UNIVERSAL_SET_TYPE:
349 final ASN1Set uuidSet = ASN1Set.decodeAsSet(e);
350 final ASN1Element[] uuidElements = uuidSet.elements();
351 entryUUIDs = new ArrayList<UUID>(uuidElements.length);
352 for (final ASN1Element uuidElement : uuidElements)
353 {
354 try
355 {
356 entryUUIDs.add(StaticUtils.decodeUUID(
357 uuidElement.getValue()));
358 }
359 catch (final ParseException pe)
360 {
361 Debug.debugException(pe);
362 throw new LDAPException(ResultCode.DECODING_ERROR,
363 ERR_SYNC_INFO_IR_INVALID_UUID.get(type.name(),
364 pe.getMessage()), pe);
365 }
366 }
367 break;
368 default:
369 throw new LDAPException(ResultCode.DECODING_ERROR,
370 ERR_SYNC_INFO_IR_VALUE_INVALID_SEQUENCE_TYPE.get(
371 type.name(), StaticUtils.toHex(e.getType())));
372 }
373 }
374
375 if (entryUUIDs == null)
376 {
377 throw new LDAPException(ResultCode.DECODING_ERROR,
378 ERR_SYNC_INFO_IR_NO_UUID_SET.get(type.name()));
379 }
380 break;
381 }
382 }
383 catch (final LDAPException le)
384 {
385 throw le;
386 }
387 catch (final Exception e)
388 {
389 Debug.debugException(e);
390
391 throw new LDAPException(ResultCode.DECODING_ERROR,
392 ERR_SYNC_INFO_IR_VALUE_DECODING_ERROR.get(
393 StaticUtils.getExceptionMessage(e)), e);
394 }
395
396 return new ContentSyncInfoIntermediateResponse(type, value, cookie,
397 refreshDone, refreshDeletes, entryUUIDs, r.getControls());
398 }
399
400
401
402 /**
403 * Encodes the provided information into a form suitable for use as the value
404 * of this intermediate response.
405 *
406 * @param type The type for this sync info message.
407 * @param cookie The updated sync state cookie.
408 * @param refreshDone Indicates whether the refresh phase of the
409 * synchronization operation is complete.
410 * @param entryUUIDs The set of entryUUIDs for the entries referenced
411 * in this message.
412 * @param refreshDeletes Indicates whether the associated entryUUIDs are for
413 * entries that have been removed.
414 *
415 * @return The encoded value.
416 */
417 private static ASN1OctetString encodeValue(final ContentSyncInfoType type,
418 final ASN1OctetString cookie,
419 final boolean refreshDone,
420 final List<UUID> entryUUIDs,
421 final boolean refreshDeletes)
422 {
423 final ASN1Element e;
424 switch (type)
425 {
426 case NEW_COOKIE:
427 e = new ASN1OctetString(type.getType(), cookie.getValue());
428 break;
429
430 case REFRESH_DELETE:
431 case REFRESH_PRESENT:
432 ArrayList<ASN1Element> l = new ArrayList<ASN1Element>(2);
433 if (cookie != null)
434 {
435 l.add(cookie);
436 }
437
438 if (! refreshDone)
439 {
440 l.add(new ASN1Boolean(refreshDone));
441 }
442
443 e = new ASN1Sequence(type.getType(), l);
444 break;
445
446 case SYNC_ID_SET:
447 l = new ArrayList<ASN1Element>(3);
448
449 if (cookie != null)
450 {
451 l.add(cookie);
452 }
453
454 if (refreshDeletes)
455 {
456 l.add(new ASN1Boolean(refreshDeletes));
457 }
458
459 final ArrayList<ASN1Element> uuidElements =
460 new ArrayList<ASN1Element>(entryUUIDs.size());
461 for (final UUID uuid : entryUUIDs)
462 {
463 uuidElements.add(new ASN1OctetString(StaticUtils.encodeUUID(uuid)));
464 }
465 l.add(new ASN1Set(uuidElements));
466
467 e = new ASN1Sequence(type.getType(), l);
468 break;
469
470 default:
471 // This should never happen.
472 throw new AssertionError("Unexpected sync info type: " + type.name());
473 }
474
475 return new ASN1OctetString(e.encode());
476 }
477
478
479
480 /**
481 * Retrieves the type of content synchronization information represented in
482 * this response.
483 *
484 * @return The type of content synchronization information represented in
485 * this response.
486 */
487 public ContentSyncInfoType getType()
488 {
489 return type;
490 }
491
492
493
494 /**
495 * Retrieves an updated state cookie for the synchronization session, if
496 * available. It will always be non-{@code null} for a type of
497 * {@link ContentSyncInfoType#NEW_COOKIE}, and may or may not be {@code null}
498 * for other types.
499 *
500 * @return An updated state cookie for the synchronization session, or
501 * {@code null} if none is available.
502 */
503 public ASN1OctetString getCookie()
504 {
505 return cookie;
506 }
507
508
509
510 /**
511 * Indicates whether the refresh phase of the synchronization operation has
512 * completed. This is only applicable for the
513 * {@link ContentSyncInfoType#REFRESH_DELETE} and
514 * {@link ContentSyncInfoType#REFRESH_PRESENT} types.
515 *
516 * @return {@code true} if the refresh phase of the synchronization operation
517 * has completed, or {@code false} if not or if it is not applicable
518 * for this message type.
519 */
520 public boolean refreshDone()
521 {
522 return refreshDone;
523 }
524
525
526
527 /**
528 * Retrieves a list of the entryUUID values for the entries referenced in this
529 * message. This is only applicable for the
530 * {@link ContentSyncInfoType#SYNC_ID_SET} type.
531 *
532 * @return A list of the entryUUID values for the entries referenced in this
533 * message, or {@code null} if it is not applicable for this message
534 * type.
535 */
536 public List<UUID> getEntryUUIDs()
537 {
538 return entryUUIDs;
539 }
540
541
542
543 /**
544 * Indicates whether the provided set of UUIDs represent entries that have
545 * been removed. This is only applicable for the
546 * {@link ContentSyncInfoType#SYNC_ID_SET} type.
547 *
548 * @return {@code true} if the associated set of entryUUIDs represent entries
549 * that have been deleted, or {@code false} if they represent entries
550 * that remain unchanged or if it is not applicable for this message
551 * type.
552 */
553 public boolean refreshDeletes()
554 {
555 return refreshDeletes;
556 }
557
558
559
560 /**
561 * {@inheritDoc}
562 */
563 @Override()
564 public String getIntermediateResponseName()
565 {
566 return INFO_INTERMEDIATE_RESPONSE_NAME_SYNC_INFO.get();
567 }
568
569
570
571 /**
572 * {@inheritDoc}
573 */
574 @Override()
575 public String valueToString()
576 {
577 final StringBuilder buffer = new StringBuilder();
578
579 buffer.append("syncInfoType='");
580 buffer.append(type.name());
581 buffer.append('\'');
582
583 if (cookie != null)
584 {
585 buffer.append(" cookie='");
586 StaticUtils.toHex(cookie.getValue(), buffer);
587 buffer.append('\'');
588 }
589
590 switch (type)
591 {
592 case REFRESH_DELETE:
593 case REFRESH_PRESENT:
594 buffer.append(" refreshDone='");
595 buffer.append(refreshDone);
596 buffer.append('\'');
597 break;
598
599 case SYNC_ID_SET:
600 buffer.append(" entryUUIDs={");
601
602 final Iterator<UUID> iterator = entryUUIDs.iterator();
603 while (iterator.hasNext())
604 {
605 buffer.append('\'');
606 buffer.append(iterator.next().toString());
607 buffer.append('\'');
608
609 if (iterator.hasNext())
610 {
611 buffer.append(',');
612 }
613 }
614
615 buffer.append('}');
616 break;
617
618 case NEW_COOKIE:
619 default:
620 // No additional content is needed.
621 break;
622 }
623
624 return buffer.toString();
625 }
626
627
628
629 /**
630 * {@inheritDoc}
631 */
632 @Override()
633 public void toString(final StringBuilder buffer)
634 {
635 buffer.append("ContentSyncInfoIntermediateResponse(");
636
637 final int messageID = getMessageID();
638 if (messageID >= 0)
639 {
640 buffer.append("messageID=");
641 buffer.append(messageID);
642 buffer.append(", ");
643 }
644
645 buffer.append("type='");
646 buffer.append(type.name());
647 buffer.append('\'');
648
649 if (cookie != null)
650 {
651 buffer.append(", cookie='");
652 StaticUtils.toHex(cookie.getValue(), buffer);
653 buffer.append("', ");
654 }
655
656 switch (type)
657 {
658 case NEW_COOKIE:
659 // No additional content is needed.
660 break;
661
662 case REFRESH_DELETE:
663 case REFRESH_PRESENT:
664 buffer.append(", refreshDone=");
665 buffer.append(refreshDone);
666 break;
667
668 case SYNC_ID_SET:
669 buffer.append(", entryUUIDs={");
670
671 final Iterator<UUID> iterator = entryUUIDs.iterator();
672 while (iterator.hasNext())
673 {
674 buffer.append('\'');
675 buffer.append(iterator.next());
676 buffer.append('\'');
677 if (iterator.hasNext())
678 {
679 buffer.append(',');
680 }
681 }
682
683 buffer.append("}, refreshDeletes=");
684 buffer.append(refreshDeletes);
685 break;
686 }
687
688 buffer.append(')');
689 }
690 }