001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.unboundidds.extensions; 037 038 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.Iterator; 044import java.util.List; 045 046import com.unboundid.asn1.ASN1Element; 047import com.unboundid.asn1.ASN1Enumerated; 048import com.unboundid.asn1.ASN1OctetString; 049import com.unboundid.asn1.ASN1Sequence; 050import com.unboundid.ldap.protocol.AddRequestProtocolOp; 051import com.unboundid.ldap.protocol.DeleteRequestProtocolOp; 052import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp; 053import com.unboundid.ldap.protocol.LDAPMessage; 054import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp; 055import com.unboundid.ldap.protocol.ModifyRequestProtocolOp; 056import com.unboundid.ldap.sdk.AddRequest; 057import com.unboundid.ldap.sdk.Control; 058import com.unboundid.ldap.sdk.DeleteRequest; 059import com.unboundid.ldap.sdk.ExtendedRequest; 060import com.unboundid.ldap.sdk.ExtendedResult; 061import com.unboundid.ldap.sdk.LDAPConnection; 062import com.unboundid.ldap.sdk.LDAPException; 063import com.unboundid.ldap.sdk.LDAPRequest; 064import com.unboundid.ldap.sdk.ModifyRequest; 065import com.unboundid.ldap.sdk.ModifyDNRequest; 066import com.unboundid.ldap.sdk.ResultCode; 067import com.unboundid.util.Debug; 068import com.unboundid.util.NotMutable; 069import com.unboundid.util.NotNull; 070import com.unboundid.util.Nullable; 071import com.unboundid.util.StaticUtils; 072import com.unboundid.util.ThreadSafety; 073import com.unboundid.util.ThreadSafetyLevel; 074 075import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 076 077 078 079/** 080 * This class provides an implementation of an extended request that can be used 081 * to send multiple update requests to the server in a single packet, optionally 082 * processing them as a single atomic unit. 083 * <BR> 084 * <BLOCKQUOTE> 085 * <B>NOTE:</B> This class, and other classes within the 086 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 087 * supported for use against Ping Identity, UnboundID, and 088 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 089 * for proprietary functionality or for external specifications that are not 090 * considered stable or mature enough to be guaranteed to work in an 091 * interoperable way with other types of LDAP servers. 092 * </BLOCKQUOTE> 093 * <BR> 094 * The OID for this request is 1.3.6.1.4.1.30221.2.6.17, and the value must have 095 * the following encoding: 096 * <BR><BR> 097 * <PRE> 098 * MultiUpdateRequestValue ::= SEQUENCE { 099 * errorBehavior ENUMERATED { 100 * atomic (0), 101 * quitOnError (1), 102 * continueOnError (2), 103 * ... }, 104 * requests SEQUENCE OF SEQUENCE { 105 * updateOp CHOICE { 106 * modifyRequest ModifyRequest, 107 * addRequest AddRequest, 108 * delRequest DelRequest, 109 * modDNRequest ModifyDNRequest, 110 * extendedReq ExtendedRequest, 111 * ... }, 112 * controls [0] Controls OPTIONAL, 113 * ... }, 114 * ... } 115 * </PRE> 116 * <BR><BR> 117 * <H2>Example</H2> 118 * The following example demonstrates the use of the multi-update extended 119 * request to create a new user entry and modify an existing group entry to add 120 * the new user as a member: 121 * <PRE> 122 * MultiUpdateExtendedRequest multiUpdateRequest = 123 * new MultiUpdateExtendedRequest( 124 * MultiUpdateErrorBehavior.ABORT_ON_ERROR, 125 * new AddRequest( 126 * "dn: uid=new.user,ou=People,dc=example,dc=com", 127 * "objectClass: top", 128 * "objectClass: person", 129 * "objectClass: organizationalPerson", 130 * "objectClass: inetOrgPerson", 131 * "uid: new.user", 132 * "givenName: New", 133 * "sn: User", 134 * "cn: New User"), 135 * new ModifyRequest( 136 * "dn: cn=Test Group,ou=Groups,dc=example,dc=com", 137 * "changetype: modify", 138 * "add: member", 139 * "member: uid=new.user,ou=People,dc=example,dc=com")); 140 * 141 * MultiUpdateExtendedResult multiUpdateResult = 142 * (MultiUpdateExtendedResult) 143 * connection.processExtendedOperation(multiUpdateRequest); 144 * if (multiUpdateResult.getResultCode() == ResultCode.SUCCESS) 145 * { 146 * // The server successfully processed the multi-update request, although 147 * // this does not necessarily mean that any or all of the changes 148 * // contained in it were successful. For that, we should look at the 149 * // changes applied and/or results element of the response. 150 * switch (multiUpdateResult.getChangesApplied()) 151 * { 152 * case NONE: 153 * // There were no changes applied. Based on the configuration of the 154 * // request, this means that the attempt to create the user failed 155 * // and there was no subsequent attempt to add that user to a group. 156 * break; 157 * case ALL: 158 * // Both parts of the update succeeded. The user was created and 159 * // successfully added to a group. 160 * break; 161 * case PARTIAL: 162 * // At least one update succeeded, and at least one failed. Based on 163 * // the configuration of the request, this means that the user was 164 * // successfully created but not added to the target group. 165 * break; 166 * } 167 * } 168 * else 169 * { 170 * // The server encountered a failure while attempting to parse or process 171 * // the multi-update operation itself and did not attempt to process any 172 * // of the changes contained in the request. 173 * } 174 * </PRE> 175 * 176 * @see MultiUpdateErrorBehavior 177 * @see MultiUpdateExtendedResult 178 */ 179@NotMutable() 180@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 181public final class MultiUpdateExtendedRequest 182 extends ExtendedRequest 183{ 184 /** 185 * The OID (1.3.6.1.4.1.30221.2.6.17) for the multi-update extended request. 186 */ 187 @NotNull public static final String MULTI_UPDATE_REQUEST_OID = 188 "1.3.6.1.4.1.30221.2.6.17"; 189 190 191 192 /** 193 * The serial version UID for this serializable class. 194 */ 195 private static final long serialVersionUID = 6101686180473949142L; 196 197 198 199 // The set of update requests to be processed. 200 @NotNull private final List<LDAPRequest> requests; 201 202 // The behavior to exhibit if an error is encountered during processing. 203 @NotNull private final MultiUpdateErrorBehavior errorBehavior; 204 205 206 207 /** 208 * Creates a new multi-update extended request with the provided information. 209 * 210 * @param errorBehavior The behavior to exhibit if errors are encountered. 211 * It must not be {@code null}. 212 * @param requests The set of requests to be processed. It must not 213 * be {@code null} or empty. Only add, delete, modify, 214 * modify DN, and certain extended requests (as 215 * determined by the server) should be included. 216 * 217 * @throws LDAPException If the set of requests includes one or more invalid 218 * request types. 219 */ 220 public MultiUpdateExtendedRequest( 221 @NotNull final MultiUpdateErrorBehavior errorBehavior, 222 @NotNull final LDAPRequest... requests) 223 throws LDAPException 224 { 225 this(errorBehavior, Arrays.asList(requests)); 226 } 227 228 229 230 /** 231 * Creates a new multi-update extended request with the provided information. 232 * 233 * @param errorBehavior The behavior to exhibit if errors are encountered. 234 * It must not be {@code null}. 235 * @param requests The set of requests to be processed. It must not 236 * be {@code null} or empty. Only add, delete, modify, 237 * modify DN, and certain extended requests (as 238 * determined by the server) should be included. Each 239 * request may include zero or more controls that 240 * should apply only to that request. 241 * @param controls The set of controls to be included in the 242 * multi-update extended request. It may be empty or 243 * {@code null} if no extended request controls are 244 * needed in the multi-update request. 245 * 246 * @throws LDAPException If the set of requests includes one or more invalid 247 * request types. 248 */ 249 public MultiUpdateExtendedRequest( 250 @NotNull final MultiUpdateErrorBehavior errorBehavior, 251 @NotNull final LDAPRequest[] requests, 252 @Nullable final Control... controls) 253 throws LDAPException 254 { 255 this(errorBehavior, Arrays.asList(requests), controls); 256 } 257 258 259 260 /** 261 * Creates a new multi-update extended request with the provided information. 262 * 263 * @param errorBehavior The behavior to exhibit if errors are encountered. 264 * It must not be {@code null}. 265 * @param requests The set of requests to be processed. It must not 266 * be {@code null} or empty. Only add, delete, modify, 267 * modify DN, and certain extended requests (as 268 * determined by the server) should be included. Each 269 * request may include zero or more controls that 270 * should apply only to that request. 271 * @param controls The set of controls to be included in the 272 * multi-update extended request. It may be empty or 273 * {@code null} if no extended request controls are 274 * needed in the multi-update request. 275 * 276 * @throws LDAPException If the set of requests includes one or more invalid 277 * request types. 278 */ 279 public MultiUpdateExtendedRequest( 280 @NotNull final MultiUpdateErrorBehavior errorBehavior, 281 @NotNull final List<LDAPRequest> requests, 282 @Nullable final Control... controls) 283 throws LDAPException 284 { 285 super(MULTI_UPDATE_REQUEST_OID, encodeValue(errorBehavior, requests), 286 controls); 287 288 this.errorBehavior = errorBehavior; 289 290 final ArrayList<LDAPRequest> requestList = new ArrayList<>(requests.size()); 291 for (final LDAPRequest r : requests) 292 { 293 switch (r.getOperationType()) 294 { 295 case ADD: 296 case DELETE: 297 case MODIFY: 298 case MODIFY_DN: 299 case EXTENDED: 300 requestList.add(r); 301 break; 302 default: 303 throw new LDAPException(ResultCode.PARAM_ERROR, 304 ERR_MULTI_UPDATE_REQUEST_INVALID_REQUEST_TYPE.get( 305 r.getOperationType().name())); 306 } 307 } 308 this.requests = Collections.unmodifiableList(requestList); 309 } 310 311 312 313 /** 314 * Creates a new multi-update extended request with the provided information. 315 * 316 * @param errorBehavior The behavior to exhibit if errors are encountered. 317 * It must not be {@code null}. 318 * @param requests The set of requests to be processed. It must not 319 * be {@code null} or empty. Only add, delete, modify, 320 * modify DN, and certain extended requests (as 321 * determined by the server) should be included. Each 322 * request may include zero or more controls that 323 * should apply only to that request. 324 * @param encodedValue The encoded representation of the value for this 325 * request. 326 * @param controls The set of controls to be included in the 327 * multi-update extended request. It may be empty or 328 * {@code null} if no extended request controls are 329 * needed in the multi-update request. 330 */ 331 private MultiUpdateExtendedRequest( 332 @NotNull final MultiUpdateErrorBehavior errorBehavior, 333 @NotNull final List<LDAPRequest> requests, 334 @NotNull final ASN1OctetString encodedValue, 335 @Nullable final Control... controls) 336 { 337 super(MULTI_UPDATE_REQUEST_OID, encodedValue, controls); 338 339 this.errorBehavior = errorBehavior; 340 this.requests = requests; 341 } 342 343 344 345 /** 346 * Creates a new multi-update extended request from the provided generic 347 * extended request. 348 * 349 * @param extendedRequest The generic extended request to use to create this 350 * multi-update extended request. 351 * 352 * @throws LDAPException If a problem occurs while decoding the request. 353 */ 354 public MultiUpdateExtendedRequest( 355 @NotNull final ExtendedRequest extendedRequest) 356 throws LDAPException 357 { 358 super(extendedRequest); 359 360 final ASN1OctetString value = extendedRequest.getValue(); 361 if (value == null) 362 { 363 throw new LDAPException(ResultCode.DECODING_ERROR, 364 ERR_MULTI_UPDATE_REQUEST_NO_VALUE.get()); 365 } 366 367 try 368 { 369 final ASN1Element[] ve = 370 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 371 372 errorBehavior = MultiUpdateErrorBehavior.valueOf( 373 ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue()); 374 if (errorBehavior == null) 375 { 376 throw new LDAPException(ResultCode.DECODING_ERROR, 377 ERR_MULTI_UPDATE_REQUEST_INVALID_ERROR_BEHAVIOR.get( 378 ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue())); 379 } 380 381 final ASN1Element[] requestSequenceElements = 382 ASN1Sequence.decodeAsSequence(ve[1]).elements(); 383 final ArrayList<LDAPRequest> rl = 384 new ArrayList<>(requestSequenceElements.length); 385 for (final ASN1Element rse : requestSequenceElements) 386 { 387 final Control[] controls; 388 final ASN1Element[] requestElements = 389 ASN1Sequence.decodeAsSequence(rse).elements(); 390 if (requestElements.length == 2) 391 { 392 controls = Control.decodeControls( 393 ASN1Sequence.decodeAsSequence(requestElements[1])); 394 } 395 else 396 { 397 controls = StaticUtils.NO_CONTROLS; 398 } 399 400 switch (requestElements[0].getType()) 401 { 402 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST: 403 rl.add(AddRequestProtocolOp.decodeProtocolOp( 404 requestElements[0]).toAddRequest(controls)); 405 break; 406 407 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST: 408 rl.add(DeleteRequestProtocolOp.decodeProtocolOp( 409 requestElements[0]).toDeleteRequest(controls)); 410 break; 411 412 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST: 413 rl.add(ExtendedRequestProtocolOp.decodeProtocolOp( 414 requestElements[0]).toExtendedRequest(controls)); 415 break; 416 417 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST: 418 rl.add(ModifyRequestProtocolOp.decodeProtocolOp( 419 requestElements[0]).toModifyRequest(controls)); 420 break; 421 422 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST: 423 rl.add(ModifyDNRequestProtocolOp.decodeProtocolOp( 424 requestElements[0]).toModifyDNRequest(controls)); 425 break; 426 427 default: 428 throw new LDAPException(ResultCode.DECODING_ERROR, 429 ERR_MULTI_UPDATE_REQUEST_INVALID_OP_TYPE.get( 430 StaticUtils.toHex(requestElements[0].getType()))); 431 } 432 } 433 434 requests = Collections.unmodifiableList(rl); 435 } 436 catch (final LDAPException le) 437 { 438 Debug.debugException(le); 439 throw le; 440 } 441 catch (final Exception e) 442 { 443 Debug.debugException(e); 444 throw new LDAPException(ResultCode.DECODING_ERROR, 445 ERR_MULTI_UPDATE_REQUEST_CANNOT_DECODE_VALUE.get( 446 StaticUtils.getExceptionMessage(e)), 447 e); 448 } 449 } 450 451 452 453 /** 454 * Generates an ASN.1 octet string suitable for use as the value of a 455 * multi-update extended request. 456 * 457 * @param errorBehavior The behavior to exhibit if errors are encountered. 458 * It must not be {@code null}. 459 * @param requests The set of requests to be processed. It must not 460 * be {@code null} or empty. Only add, delete, modify, 461 * modify DN, and certain extended requests (as 462 * determined by the server) should be included. Each 463 * request may include zero or more controls that 464 * should apply only to that request. 465 * 466 * @return An ASN.1 octet string suitable for use as the value of a 467 * multi-update extended request. 468 */ 469 @NotNull() 470 private static ASN1OctetString encodeValue( 471 @NotNull final MultiUpdateErrorBehavior errorBehavior, 472 @NotNull final List<LDAPRequest> requests) 473 { 474 final ArrayList<ASN1Element> requestElements = 475 new ArrayList<>(requests.size()); 476 for (final LDAPRequest r : requests) 477 { 478 final ArrayList<ASN1Element> rsElements = new ArrayList<>(2); 479 switch (r.getOperationType()) 480 { 481 case ADD: 482 rsElements.add(((AddRequest) r).encodeProtocolOp()); 483 break; 484 case DELETE: 485 rsElements.add(((DeleteRequest) r).encodeProtocolOp()); 486 break; 487 case MODIFY: 488 rsElements.add(((ModifyRequest) r).encodeProtocolOp()); 489 break; 490 case MODIFY_DN: 491 rsElements.add(((ModifyDNRequest) r).encodeProtocolOp()); 492 break; 493 case EXTENDED: 494 rsElements.add(((ExtendedRequest) r).encodeProtocolOp()); 495 break; 496 } 497 498 if (r.hasControl()) 499 { 500 rsElements.add(Control.encodeControls(r.getControls())); 501 } 502 503 requestElements.add(new ASN1Sequence(rsElements)); 504 } 505 506 final ASN1Sequence valueSequence = new ASN1Sequence( 507 new ASN1Enumerated(errorBehavior.intValue()), 508 new ASN1Sequence(requestElements)); 509 return new ASN1OctetString(valueSequence.encode()); 510 } 511 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override() 518 @NotNull() 519 public MultiUpdateExtendedResult process( 520 @NotNull final LDAPConnection connection, final int depth) 521 throws LDAPException 522 { 523 final ExtendedResult extendedResponse = super.process(connection, depth); 524 return new MultiUpdateExtendedResult(extendedResponse); 525 } 526 527 528 529 /** 530 * Retrieves the behavior to exhibit if errors are encountered. 531 * 532 * @return The behavior to exhibit if errors are encountered. 533 */ 534 @NotNull() 535 public MultiUpdateErrorBehavior getErrorBehavior() 536 { 537 return errorBehavior; 538 } 539 540 541 542 /** 543 * 544 * Retrieves the set of requests to be processed. 545 * 546 * @return The set of requests to be processed. 547 */ 548 @NotNull() 549 public List<LDAPRequest> getRequests() 550 { 551 return requests; 552 } 553 554 555 556 /** 557 * {@inheritDoc} 558 */ 559 @Override() 560 @NotNull() 561 public MultiUpdateExtendedRequest duplicate() 562 { 563 return duplicate(getControls()); 564 } 565 566 567 568 /** 569 * {@inheritDoc} 570 */ 571 @Override() 572 @NotNull() 573 public MultiUpdateExtendedRequest duplicate( 574 @Nullable final Control[] controls) 575 { 576 final MultiUpdateExtendedRequest r = 577 new MultiUpdateExtendedRequest(errorBehavior, requests, 578 getValue(), controls); 579 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 580 r.setIntermediateResponseListener(getIntermediateResponseListener()); 581 r.setReferralDepth(getReferralDepth()); 582 r.setReferralConnector(getReferralConnectorInternal()); 583 return r; 584 } 585 586 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override() 592 @NotNull() 593 public String getExtendedRequestName() 594 { 595 return INFO_EXTENDED_REQUEST_NAME_MULTI_UPDATE.get(); 596 } 597 598 599 600 /** 601 * {@inheritDoc} 602 */ 603 @Override() 604 public void toString(@NotNull final StringBuilder buffer) 605 { 606 buffer.append("MultiUpdateExtendedRequest(errorBehavior="); 607 buffer.append(errorBehavior.name()); 608 buffer.append(", requests={"); 609 610 final Iterator<LDAPRequest> iterator = requests.iterator(); 611 while (iterator.hasNext()) 612 { 613 iterator.next().toString(buffer); 614 if (iterator.hasNext()) 615 { 616 buffer.append(", "); 617 } 618 } 619 620 buffer.append('}'); 621 622 final Control[] controls = getControls(); 623 if (controls.length > 0) 624 { 625 buffer.append(", controls={"); 626 for (int i=0; i < controls.length; i++) 627 { 628 if (i > 0) 629 { 630 buffer.append(", "); 631 } 632 633 buffer.append(controls[i]); 634 } 635 buffer.append('}'); 636 } 637 638 buffer.append(')'); 639 } 640}