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