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