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 }