001/* 002 * Copyright 2008-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2018 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.NotMutable; 037import com.unboundid.util.ThreadSafety; 038import com.unboundid.util.ThreadSafetyLevel; 039 040import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 041import static com.unboundid.util.Debug.*; 042import static com.unboundid.util.StaticUtils.*; 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 Alcatel-Lucent 8661 084 * server products. These classes provide support for proprietary 085 * functionality or for external specifications that are not considered stable 086 * or mature enough to be guaranteed to work in an interoperable way with 087 * 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 // This is an ugly hack to prevent checkstyle from complaining about imports 208 // for classes that are needed by javadoc @link elements but aren't otherwise 209 // used in the class. It appears that checkstyle does not recognize the use 210 // of these classes in javadoc @link elements so we must ensure that they are 211 // referenced elsewhere in the class to prevent checkstyle from complaining. 212 static 213 { 214 final InteractiveTransactionSpecificationRequestControl c = null; 215 } 216 217 218 219 /** 220 * Creates a new start interactive transaction extended request with no base 221 * DN. 222 */ 223 public StartInteractiveTransactionExtendedRequest() 224 { 225 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID); 226 227 baseDN = null; 228 } 229 230 231 232 /** 233 * Creates a new start interactive transaction extended request. 234 * 235 * @param baseDN The base DN to use for the request. It may be {@code null} 236 * if no base DN should be provided. 237 */ 238 public StartInteractiveTransactionExtendedRequest(final String baseDN) 239 { 240 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN)); 241 242 this.baseDN = baseDN; 243 } 244 245 246 247 /** 248 * Creates a new start interactive transaction extended request. 249 * 250 * @param baseDN The base DN to use for the request. It may be 251 * {@code null} if no base DN should be provided. 252 * @param controls The set of controls to include in the request. 253 */ 254 public StartInteractiveTransactionExtendedRequest(final String baseDN, 255 final Control[] controls) 256 { 257 super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN), 258 controls); 259 260 this.baseDN = baseDN; 261 } 262 263 264 265 /** 266 * Creates a new start interactive transaction extended request from the 267 * provided generic extended request. 268 * 269 * @param extendedRequest The generic extended request to use to create this 270 * start interactive transaction extended request. 271 * 272 * @throws LDAPException If a problem occurs while decoding the request. 273 */ 274 public StartInteractiveTransactionExtendedRequest( 275 final ExtendedRequest extendedRequest) 276 throws LDAPException 277 { 278 super(extendedRequest); 279 280 if (! extendedRequest.hasValue()) 281 { 282 baseDN = null; 283 return; 284 } 285 286 String baseDNStr = null; 287 try 288 { 289 final ASN1Element valueElement = 290 ASN1Element.decode(extendedRequest.getValue().getValue()); 291 final ASN1Sequence valueSequence = 292 ASN1Sequence.decodeAsSequence(valueElement); 293 for (final ASN1Element e : valueSequence.elements()) 294 { 295 if (e.getType() == TYPE_BASE_DN) 296 { 297 baseDNStr = ASN1OctetString.decodeAsOctetString(e).stringValue(); 298 } 299 else 300 { 301 throw new LDAPException(ResultCode.DECODING_ERROR, 302 ERR_START_INT_TXN_REQUEST_INVALID_ELEMENT.get( 303 toHex(e.getType()))); 304 } 305 } 306 } 307 catch (final LDAPException le) 308 { 309 debugException(le); 310 throw le; 311 } 312 catch (final Exception e) 313 { 314 debugException(e); 315 throw new LDAPException(ResultCode.DECODING_ERROR, 316 ERR_START_INT_TXN_REQUEST_VALUE_NOT_SEQUENCE.get(e.getMessage()), e); 317 } 318 319 baseDN = baseDNStr; 320 } 321 322 323 324 /** 325 * Encodes the provided information into an ASN.1 octet string suitable for 326 * use as the value of this extended request. 327 * 328 * @param baseDN The base DN to use for the request. It may be {@code null} 329 * if no base DN should be provided. 330 * 331 * @return The ASN.1 octet string containing the encoded value, or 332 * {@code null} if no value should be used. 333 */ 334 private static ASN1OctetString encodeValue(final String baseDN) 335 { 336 if (baseDN == null) 337 { 338 return null; 339 } 340 341 final ASN1Element[] elements = 342 { 343 new ASN1OctetString(TYPE_BASE_DN, baseDN) 344 }; 345 346 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 347 } 348 349 350 351 /** 352 * Retrieves the base DN for this start interactive transaction extended 353 * request, if available. 354 * 355 * @return The base DN for this start interactive transaction extended 356 * request, or {@code null} if none was provided. 357 */ 358 public String getBaseDN() 359 { 360 return baseDN; 361 } 362 363 364 365 /** 366 * {@inheritDoc} 367 */ 368 @Override() 369 public StartInteractiveTransactionExtendedResult process( 370 final LDAPConnection connection, final int depth) 371 throws LDAPException 372 { 373 final ExtendedResult extendedResponse = super.process(connection, depth); 374 return new StartInteractiveTransactionExtendedResult(extendedResponse); 375 } 376 377 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override() 383 public StartInteractiveTransactionExtendedRequest duplicate() 384 { 385 return duplicate(getControls()); 386 } 387 388 389 390 /** 391 * {@inheritDoc} 392 */ 393 @Override() 394 public StartInteractiveTransactionExtendedRequest duplicate( 395 final Control[] controls) 396 { 397 final StartInteractiveTransactionExtendedRequest r = 398 new StartInteractiveTransactionExtendedRequest(baseDN, controls); 399 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 400 return r; 401 } 402 403 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override() 409 public String getExtendedRequestName() 410 { 411 return INFO_EXTENDED_REQUEST_NAME_START_INTERACTIVE_TXN.get(); 412 } 413 414 415 416 /** 417 * {@inheritDoc} 418 */ 419 @Override() 420 public void toString(final StringBuilder buffer) 421 { 422 buffer.append("StartInteractiveTransactionExtendedRequest("); 423 424 if (baseDN != null) 425 { 426 buffer.append("baseDN='"); 427 buffer.append(baseDN); 428 buffer.append('\''); 429 } 430 431 final Control[] controls = getControls(); 432 if (controls.length > 0) 433 { 434 if (baseDN != null) 435 { 436 buffer.append(", "); 437 } 438 buffer.append("controls={"); 439 for (int i=0; i < controls.length; i++) 440 { 441 if (i > 0) 442 { 443 buffer.append(", "); 444 } 445 446 buffer.append(controls[i]); 447 } 448 buffer.append('}'); 449 } 450 451 buffer.append(')'); 452 } 453}