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 com.unboundid.asn1.ASN1Element;
026 import com.unboundid.asn1.ASN1OctetString;
027 import com.unboundid.asn1.ASN1Sequence;
028 import com.unboundid.ldap.sdk.Control;
029 import com.unboundid.ldap.sdk.ExtendedRequest;
030 import com.unboundid.ldap.sdk.ExtendedResult;
031 import com.unboundid.ldap.sdk.LDAPConnection;
032 import com.unboundid.ldap.sdk.LDAPException;
033 import com.unboundid.ldap.sdk.ResultCode;
034 import com.unboundid.ldap.sdk.unboundidds.controls.
035 InteractiveTransactionSpecificationRequestControl;
036 import com.unboundid.util.NotMutable;
037 import com.unboundid.util.ThreadSafety;
038 import com.unboundid.util.ThreadSafetyLevel;
039
040 import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
041 import static com.unboundid.util.Debug.*;
042 import static com.unboundid.util.StaticUtils.*;
043
044
045
046 /**
047 * <BLOCKQUOTE>
048 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
049 * LDAP SDK for Java. It is not available for use in applications that
050 * include only the Standard Edition of the LDAP SDK, and is not supported for
051 * use in conjunction with non-UnboundID products.
052 * </BLOCKQUOTE>
053 * <BR><BR>
054 * <BLOCKQUOTE>
055 * <B>NOTE:</B> The use of interactive transactions is discouraged because it
056 * can create conditions which are prone to deadlocks between operations that
057 * may result in the cancellation of one or both operations. It is strongly
058 * recommended that standard LDAP transactions (which may be started using a
059 * {@link com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest})
060 * or a multi-update extended operation be used instead. Although they cannot
061 * include arbitrary read operations, LDAP transactions and multi-update
062 * operations may be used in conjunction with the
063 * {@link com.unboundid.ldap.sdk.controls.AssertionRequestControl},
064 * {@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}, and
065 * {@link com.unboundid.ldap.sdk.controls.PostReadRequestControl} to
066 * incorporate some read capability into a transaction, and in conjunction
067 * with the {@link com.unboundid.ldap.sdk.ModificationType#INCREMENT}
068 * modification type to increment integer values without the need to know the
069 * precise value before or after the operation (although the pre-read and/or
070 * post-read controls may be used to determine that).
071 * </BLOCKQUOTE>
072 * This class provides an implementation of the start interactive transaction
073 * extended request. It may be used to begin a transaction that allows multiple
074 * operations to be processed as a single atomic unit. Interactive transactions
075 * may include read operations, in which case it is guaranteed that no
076 * operations outside of the transaction will be allowed to access the
077 * associated entries until the transaction has been committed or aborted. The
078 * {@link StartInteractiveTransactionExtendedResult} that is returned will
079 * include a a transaction ID, which should be included in each operation that
080 * is part of the transaction using the
081 * {@link InteractiveTransactionSpecificationRequestControl}. After all
082 * requests for the transaction have been submitted to the server, the
083 * {@link EndInteractiveTransactionExtendedRequest} should be used to
084 * commit that transaction, or it may also be used to abort the transaction if
085 * it is decided that it is no longer needed.
086 * <BR><BR>
087 * The start transaction extended request may include an element which indicates
088 * the base DN below which all operations will be attempted. This may be used
089 * to allow the Directory Server to tailor the transaction to the appropriate
090 * backend.
091 * <BR><BR>
092 * Whenever the client sends a start interactive transaction request to the
093 * server, the {@link StartInteractiveTransactionExtendedResult} that is
094 * returned will include a transaction ID that may be used to identify the
095 * transaction for all operations which are to be performed as part of the
096 * transaction. This transaction ID should be included in a
097 * {@link InteractiveTransactionSpecificationRequestControl} attached to each
098 * request that is to be processed as part of the transaction. When the
099 * transaction has completed, the
100 * {@link EndInteractiveTransactionExtendedRequest} may be used to commit it,
101 * and it may also be used at any time to abort the transaction if it is no
102 * longer needed.
103 * <H2>Example</H2>
104 * The following example demonstrates the process for creating an interactive
105 * transaction, processing multiple requests as part of that transaction, and
106 * then commits the transaction.
107 * <PRE>
108 * // Start the interactive transaction and get the transaction ID.
109 * StartInteractiveTransactionExtendedRequest startTxnRequest =
110 * new StartInteractiveTransactionExtendedRequest("dc=example,dc=com");
111 * StartInteractiveTransactionExtendedResult startTxnResult =
112 * (StartInteractiveTransactionExtendedResult)
113 * connection.processExtendedOperation(startTxnRequest);
114 * if (startTxnResult.getResultCode() != ResultCode.SUCCESS)
115 * {
116 * throw new LDAPException(startTxnResult);
117 * }
118 * ASN1OctetString txnID = startTxnResult.getTransactionID();
119 *
120 * // At this point, we have a valid transaction. We want to ensure that the
121 * // transaction is aborted if any failure occurs, so do that in a
122 * // try-finally block.
123 * boolean txnFailed = true;
124 * try
125 * {
126 * // Perform a search to find all users in the "Sales" department.
127 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
128 * SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales"));
129 * searchRequest.addControl(
130 * new InteractiveTransactionSpecificationRequestControl(txnID, true,
131 * true));
132 *
133 * SearchResult searchResult = connection.search(searchRequest);
134 * if (searchResult.getResultCode() != ResultCode.SUCCESS)
135 * {
136 * throw new LDAPException(searchResult);
137 * }
138 *
139 * // Iterate through all of the users and assign a new fax number to each
140 * // of them.
141 * for (SearchResultEntry e : searchResult.getSearchEntries())
142 * {
143 * ModifyRequest modifyRequest = new ModifyRequest(e.getDN(),
144 * new Modification(ModificationType.REPLACE,
145 * "facsimileTelephoneNumber", "+1 123 456 7890"));
146 * modifyRequest.addControl(
147 * new InteractiveTransactionSpecificationRequestControl(txnID, true,
148 *
149 * true));
150 * connection.modify(modifyRequest);
151 * }
152 *
153 * // Commit the transaction.
154 * ExtendedResult endTxnResult = connection.processExtendedOperation(
155 * new EndInteractiveTransactionExtendedRequest(txnID, true));
156 * if (endTxnResult.getResultCode() == ResultCode.SUCCESS)
157 * {
158 * txnFailed = false;
159 * }
160 * }
161 * finally
162 * {
163 * if (txnFailed)
164 * {
165 * connection.processExtendedOperation(
166 * new EndInteractiveTransactionExtendedRequest(txnID, false));
167 * }
168 * }
169 * </PRE>
170 */
171 @NotMutable()
172 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
173 public final class StartInteractiveTransactionExtendedRequest
174 extends ExtendedRequest
175 {
176 /**
177 * The OID (1.3.6.1.4.1.30221.2.6.3) for the start interactive transaction
178 * extended request.
179 */
180 public static final String START_INTERACTIVE_TRANSACTION_REQUEST_OID =
181 "1.3.6.1.4.1.30221.2.6.3";
182
183
184
185 /**
186 * The BER type for the {@code baseDN} element of the request.
187 */
188 private static final byte TYPE_BASE_DN = (byte) 0x80;
189
190
191
192 /**
193 * The serial version UID for this serializable class.
194 */
195 private static final long serialVersionUID = 4475028061132753546L;
196
197
198
199 // The base DN for this request, if specified.
200 private final String baseDN;
201
202
203
204 // This is an ugly hack to prevent checkstyle from complaining about imports
205 // for classes that are needed by javadoc @link elements but aren't otherwise
206 // used in the class. It appears that checkstyle does not recognize the use
207 // of these classes in javadoc @link elements so we must ensure that they are
208 // referenced elsewhere in the class to prevent checkstyle from complaining.
209 static
210 {
211 final InteractiveTransactionSpecificationRequestControl c = null;
212 }
213
214
215
216 /**
217 * Creates a new start interactive transaction extended request with no base
218 * DN.
219 */
220 public StartInteractiveTransactionExtendedRequest()
221 {
222 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID);
223
224 baseDN = null;
225 }
226
227
228
229 /**
230 * Creates a new start interactive transaction extended request.
231 *
232 * @param baseDN The base DN to use for the request. It may be {@code null}
233 * if no base DN should be provided.
234 */
235 public StartInteractiveTransactionExtendedRequest(final String baseDN)
236 {
237 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN));
238
239 this.baseDN = baseDN;
240 }
241
242
243
244 /**
245 * Creates a new start interactive transaction extended request.
246 *
247 * @param baseDN The base DN to use for the request. It may be
248 * {@code null} if no base DN should be provided.
249 * @param controls The set of controls to include in the request.
250 */
251 public StartInteractiveTransactionExtendedRequest(final String baseDN,
252 final Control[] controls)
253 {
254 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN),
255 controls);
256
257 this.baseDN = baseDN;
258 }
259
260
261
262 /**
263 * Creates a new start interactive transaction extended request from the
264 * provided generic extended request.
265 *
266 * @param extendedRequest The generic extended request to use to create this
267 * start interactive transaction extended request.
268 *
269 * @throws LDAPException If a problem occurs while decoding the request.
270 */
271 public StartInteractiveTransactionExtendedRequest(
272 final ExtendedRequest extendedRequest)
273 throws LDAPException
274 {
275 super(extendedRequest);
276
277 if (! extendedRequest.hasValue())
278 {
279 baseDN = null;
280 return;
281 }
282
283 String baseDNStr = null;
284 try
285 {
286 final ASN1Element valueElement =
287 ASN1Element.decode(extendedRequest.getValue().getValue());
288 final ASN1Sequence valueSequence =
289 ASN1Sequence.decodeAsSequence(valueElement);
290 for (final ASN1Element e : valueSequence.elements())
291 {
292 if (e.getType() == TYPE_BASE_DN)
293 {
294 baseDNStr = ASN1OctetString.decodeAsOctetString(e).stringValue();
295 }
296 else
297 {
298 throw new LDAPException(ResultCode.DECODING_ERROR,
299 ERR_START_INT_TXN_REQUEST_INVALID_ELEMENT.get(
300 toHex(e.getType())));
301 }
302 }
303 }
304 catch (LDAPException le)
305 {
306 debugException(le);
307 throw le;
308 }
309 catch (Exception e)
310 {
311 debugException(e);
312 throw new LDAPException(ResultCode.DECODING_ERROR,
313 ERR_START_INT_TXN_REQUEST_VALUE_NOT_SEQUENCE.get(e.getMessage()), e);
314 }
315
316 baseDN = baseDNStr;
317 }
318
319
320
321 /**
322 * Encodes the provided information into an ASN.1 octet string suitable for
323 * use as the value of this extended request.
324 *
325 * @param baseDN The base DN to use for the request. It may be {@code null}
326 * if no base DN should be provided.
327 *
328 * @return The ASN.1 octet string containing the encoded value, or
329 * {@code null} if no value should be used.
330 */
331 private static ASN1OctetString encodeValue(final String baseDN)
332 {
333 if (baseDN == null)
334 {
335 return null;
336 }
337
338 final ASN1Element[] elements =
339 {
340 new ASN1OctetString(TYPE_BASE_DN, baseDN)
341 };
342
343 return new ASN1OctetString(new ASN1Sequence(elements).encode());
344 }
345
346
347
348 /**
349 * Retrieves the base DN for this start interactive transaction extended
350 * request, if available.
351 *
352 * @return The base DN for this start interactive transaction extended
353 * request, or {@code null} if none was provided.
354 */
355 public String getBaseDN()
356 {
357 return baseDN;
358 }
359
360
361
362 /**
363 * {@inheritDoc}
364 */
365 @Override()
366 public StartInteractiveTransactionExtendedResult process(
367 final LDAPConnection connection, final int depth)
368 throws LDAPException
369 {
370 final ExtendedResult extendedResponse = super.process(connection, depth);
371 return new StartInteractiveTransactionExtendedResult(extendedResponse);
372 }
373
374
375
376 /**
377 * {@inheritDoc}
378 */
379 @Override()
380 public StartInteractiveTransactionExtendedRequest duplicate()
381 {
382 return duplicate(getControls());
383 }
384
385
386
387 /**
388 * {@inheritDoc}
389 */
390 @Override()
391 public StartInteractiveTransactionExtendedRequest duplicate(
392 final Control[] controls)
393 {
394 final StartInteractiveTransactionExtendedRequest r =
395 new StartInteractiveTransactionExtendedRequest(baseDN, controls);
396 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
397 return r;
398 }
399
400
401
402 /**
403 * {@inheritDoc}
404 */
405 @Override()
406 public String getExtendedRequestName()
407 {
408 return INFO_EXTENDED_REQUEST_NAME_START_INTERACTIVE_TXN.get();
409 }
410
411
412
413 /**
414 * {@inheritDoc}
415 */
416 @Override()
417 public void toString(final StringBuilder buffer)
418 {
419 buffer.append("StartInteractiveTransactionExtendedRequest(");
420
421 if (baseDN != null)
422 {
423 buffer.append("baseDN='");
424 buffer.append(baseDN);
425 buffer.append('\'');
426 }
427
428 final Control[] controls = getControls();
429 if (controls.length > 0)
430 {
431 if (baseDN != null)
432 {
433 buffer.append(", ");
434 }
435 buffer.append("controls={");
436 for (int i=0; i < controls.length; i++)
437 {
438 if (i > 0)
439 {
440 buffer.append(", ");
441 }
442
443 buffer.append(controls[i]);
444 }
445 buffer.append('}');
446 }
447
448 buffer.append(')');
449 }
450 }