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 */ 021package com.unboundid.ldap.sdk.controls; 022 023 024 025import java.text.ParseException; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Iterator; 029import java.util.List; 030import java.util.UUID; 031 032import com.unboundid.asn1.ASN1Boolean; 033import com.unboundid.asn1.ASN1Constants; 034import com.unboundid.asn1.ASN1Element; 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.asn1.ASN1Sequence; 037import com.unboundid.asn1.ASN1Set; 038import com.unboundid.ldap.sdk.Control; 039import com.unboundid.ldap.sdk.IntermediateResponse; 040import com.unboundid.ldap.sdk.LDAPException; 041import com.unboundid.ldap.sdk.ResultCode; 042import com.unboundid.util.Debug; 043import com.unboundid.util.NotMutable; 044import com.unboundid.util.StaticUtils; 045import com.unboundid.util.ThreadSafety; 046import com.unboundid.util.ThreadSafetyLevel; 047import com.unboundid.util.Validator; 048 049import 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) 065public 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}