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