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