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 }