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 Alcatel-Lucent 8661 071 * server products. These classes provide support for proprietary 072 * functionality or for external specifications that are not considered stable 073 * or mature enough to be guaranteed to work in an interoperable way with 074 * 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 = 274 new ArrayList<LDAPRequest>(requests.size()); 275 for (final LDAPRequest r : requests) 276 { 277 switch (r.getOperationType()) 278 { 279 case ADD: 280 case DELETE: 281 case MODIFY: 282 case MODIFY_DN: 283 case EXTENDED: 284 requestList.add(r); 285 break; 286 default: 287 throw new LDAPException(ResultCode.PARAM_ERROR, 288 ERR_MULTI_UPDATE_REQUEST_INVALID_REQUEST_TYPE.get( 289 r.getOperationType().name())); 290 } 291 } 292 this.requests = Collections.unmodifiableList(requestList); 293 } 294 295 296 297 /** 298 * Creates a new multi-update extended request with the provided information. 299 * 300 * @param errorBehavior The behavior to exhibit if errors are encountered. 301 * It must not be {@code null}. 302 * @param requests The set of requests to be processed. It must not 303 * be {@code null} or empty. Only add, delete, modify, 304 * modify DN, and certain extended requests (as 305 * determined by the server) should be included. Each 306 * request may include zero or more controls that 307 * should apply only to that request. 308 * @param encodedValue The encoded representation of the value for this 309 * request. 310 * @param controls The set of controls to be included in the 311 * multi-update extended request. It may be empty or 312 * {@code null} if no extended request controls are 313 * needed in the multi-update request. 314 */ 315 private MultiUpdateExtendedRequest( 316 final MultiUpdateErrorBehavior errorBehavior, 317 final List<LDAPRequest> requests, 318 final ASN1OctetString encodedValue, 319 final Control... controls) 320 { 321 super(MULTI_UPDATE_REQUEST_OID, encodedValue, controls); 322 323 this.errorBehavior = errorBehavior; 324 this.requests = requests; 325 } 326 327 328 329 /** 330 * Creates a new multi-update extended request from the provided generic 331 * extended request. 332 * 333 * @param extendedRequest The generic extended request to use to create this 334 * multi-update extended request. 335 * 336 * @throws LDAPException If a problem occurs while decoding the request. 337 */ 338 public MultiUpdateExtendedRequest(final ExtendedRequest extendedRequest) 339 throws LDAPException 340 { 341 super(extendedRequest); 342 343 final ASN1OctetString value = extendedRequest.getValue(); 344 if (value == null) 345 { 346 throw new LDAPException(ResultCode.DECODING_ERROR, 347 ERR_MULTI_UPDATE_REQUEST_NO_VALUE.get()); 348 } 349 350 try 351 { 352 final ASN1Element[] ve = 353 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 354 355 errorBehavior = MultiUpdateErrorBehavior.valueOf( 356 ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue()); 357 if (errorBehavior == null) 358 { 359 throw new LDAPException(ResultCode.DECODING_ERROR, 360 ERR_MULTI_UPDATE_REQUEST_INVALID_ERROR_BEHAVIOR.get( 361 ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue())); 362 } 363 364 final ASN1Element[] requestSequenceElements = 365 ASN1Sequence.decodeAsSequence(ve[1]).elements(); 366 final ArrayList<LDAPRequest> rl = 367 new ArrayList<LDAPRequest>(requestSequenceElements.length); 368 for (final ASN1Element rse : requestSequenceElements) 369 { 370 final Control[] controls; 371 final ASN1Element[] requestElements = 372 ASN1Sequence.decodeAsSequence(rse).elements(); 373 if (requestElements.length == 2) 374 { 375 controls = Control.decodeControls( 376 ASN1Sequence.decodeAsSequence(requestElements[1])); 377 } 378 else 379 { 380 controls = StaticUtils.NO_CONTROLS; 381 } 382 383 switch (requestElements[0].getType()) 384 { 385 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST: 386 rl.add(AddRequestProtocolOp.decodeProtocolOp( 387 requestElements[0]).toAddRequest(controls)); 388 break; 389 390 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST: 391 rl.add(DeleteRequestProtocolOp.decodeProtocolOp( 392 requestElements[0]).toDeleteRequest(controls)); 393 break; 394 395 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST: 396 rl.add(ExtendedRequestProtocolOp.decodeProtocolOp( 397 requestElements[0]).toExtendedRequest(controls)); 398 break; 399 400 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST: 401 rl.add(ModifyRequestProtocolOp.decodeProtocolOp( 402 requestElements[0]).toModifyRequest(controls)); 403 break; 404 405 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST: 406 rl.add(ModifyDNRequestProtocolOp.decodeProtocolOp( 407 requestElements[0]).toModifyDNRequest(controls)); 408 break; 409 410 default: 411 throw new LDAPException(ResultCode.DECODING_ERROR, 412 ERR_MULTI_UPDATE_REQUEST_INVALID_OP_TYPE.get( 413 StaticUtils.toHex(requestElements[0].getType()))); 414 } 415 } 416 417 requests = Collections.unmodifiableList(rl); 418 } 419 catch (final LDAPException le) 420 { 421 Debug.debugException(le); 422 throw le; 423 } 424 catch (final Exception e) 425 { 426 Debug.debugException(e); 427 throw new LDAPException(ResultCode.DECODING_ERROR, 428 ERR_MULTI_UPDATE_REQUEST_CANNOT_DECODE_VALUE.get( 429 StaticUtils.getExceptionMessage(e)), 430 e); 431 } 432 } 433 434 435 436 /** 437 * Generates an ASN.1 octet string suitable for use as the value of a 438 * multi-update extended request. 439 * 440 * @param errorBehavior The behavior to exhibit if errors are encountered. 441 * It must not be {@code null}. 442 * @param requests The set of requests to be processed. It must not 443 * be {@code null} or empty. Only add, delete, modify, 444 * modify DN, and certain extended requests (as 445 * determined by the server) should be included. Each 446 * request may include zero or more controls that 447 * should apply only to that request. 448 * 449 * @return An ASN.1 octet string suitable for use as the value of a 450 * multi-update extended request. 451 */ 452 private static ASN1OctetString encodeValue( 453 final MultiUpdateErrorBehavior errorBehavior, 454 final List<LDAPRequest> requests) 455 { 456 final ArrayList<ASN1Element> requestElements = new 457 ArrayList<ASN1Element>(requests.size()); 458 for (final LDAPRequest r : requests) 459 { 460 final ArrayList<ASN1Element> rsElements = new ArrayList<ASN1Element>(2); 461 switch (r.getOperationType()) 462 { 463 case ADD: 464 rsElements.add(((AddRequest) r).encodeProtocolOp()); 465 break; 466 case DELETE: 467 rsElements.add(((DeleteRequest) r).encodeProtocolOp()); 468 break; 469 case MODIFY: 470 rsElements.add(((ModifyRequest) r).encodeProtocolOp()); 471 break; 472 case MODIFY_DN: 473 rsElements.add(((ModifyDNRequest) r).encodeProtocolOp()); 474 break; 475 case EXTENDED: 476 rsElements.add(((ExtendedRequest) r).encodeProtocolOp()); 477 break; 478 } 479 480 if (r.hasControl()) 481 { 482 rsElements.add(Control.encodeControls(r.getControls())); 483 } 484 485 requestElements.add(new ASN1Sequence(rsElements)); 486 } 487 488 final ASN1Sequence valueSequence = new ASN1Sequence( 489 new ASN1Enumerated(errorBehavior.intValue()), 490 new ASN1Sequence(requestElements)); 491 return new ASN1OctetString(valueSequence.encode()); 492 } 493 494 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Override() 500 public MultiUpdateExtendedResult process(final LDAPConnection connection, 501 final int depth) 502 throws LDAPException 503 { 504 final ExtendedResult extendedResponse = super.process(connection, depth); 505 return new MultiUpdateExtendedResult(extendedResponse); 506 } 507 508 509 510 /** 511 * Retrieves the behavior to exhibit if errors are encountered. 512 * 513 * @return The behavior to exhibit if errors are encountered. 514 */ 515 public MultiUpdateErrorBehavior getErrorBehavior() 516 { 517 return errorBehavior; 518 } 519 520 521 522 /** 523 * 524 * Retrieves the set of requests to be processed. 525 * 526 * @return The set of requests to be processed. 527 */ 528 public List<LDAPRequest> getRequests() 529 { 530 return requests; 531 } 532 533 534 535 /** 536 * {@inheritDoc} 537 */ 538 @Override() 539 public MultiUpdateExtendedRequest duplicate() 540 { 541 return duplicate(getControls()); 542 } 543 544 545 546 /** 547 * {@inheritDoc} 548 */ 549 @Override() 550 public MultiUpdateExtendedRequest duplicate(final Control[] controls) 551 { 552 final MultiUpdateExtendedRequest r = 553 new MultiUpdateExtendedRequest(errorBehavior, requests, 554 getValue(), controls); 555 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 556 return r; 557 } 558 559 560 561 /** 562 * {@inheritDoc} 563 */ 564 @Override() 565 public String getExtendedRequestName() 566 { 567 return INFO_EXTENDED_REQUEST_NAME_MULTI_UPDATE.get(); 568 } 569 570 571 572 /** 573 * {@inheritDoc} 574 */ 575 @Override() 576 public void toString(final StringBuilder buffer) 577 { 578 buffer.append("MultiUpdateExtendedRequest(errorBehavior="); 579 buffer.append(errorBehavior.name()); 580 buffer.append(", requests={"); 581 582 final Iterator<LDAPRequest> iterator = requests.iterator(); 583 while (iterator.hasNext()) 584 { 585 iterator.next().toString(buffer); 586 if (iterator.hasNext()) 587 { 588 buffer.append(", "); 589 } 590 } 591 592 buffer.append('}'); 593 594 final Control[] controls = getControls(); 595 if (controls.length > 0) 596 { 597 buffer.append(", controls={"); 598 for (int i=0; i < controls.length; i++) 599 { 600 if (i > 0) 601 { 602 buffer.append(", "); 603 } 604 605 buffer.append(controls[i]); 606 } 607 buffer.append('}'); 608 } 609 610 buffer.append(')'); 611 } 612}