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