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