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 java.util.ArrayList; 026import java.util.Collections; 027import java.util.List; 028 029import com.unboundid.asn1.ASN1Element; 030import com.unboundid.asn1.ASN1OctetString; 031import com.unboundid.asn1.ASN1Sequence; 032import com.unboundid.ldap.sdk.Control; 033import com.unboundid.ldap.sdk.ExtendedResult; 034import com.unboundid.ldap.sdk.LDAPException; 035import com.unboundid.ldap.sdk.ResultCode; 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 implements a data structure for storing the information from an 066 * extended result for the start interactive transaction extended request. It 067 * is able to decode a generic extended result to extract the transaction ID and 068 * base DNs that it may contain, if the operation was successful. 069 * <BR> 070 * <BLOCKQUOTE> 071 * <B>NOTE:</B> This class, and other classes within the 072 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 073 * supported for use against Ping Identity, UnboundID, and 074 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 075 * for proprietary functionality or for external specifications that are not 076 * considered stable or mature enough to be guaranteed to work in an 077 * interoperable way with other types of LDAP servers. 078 * </BLOCKQUOTE> 079 * <BR> 080 * See the documentation for the 081 * {@link StartInteractiveTransactionExtendedRequest} class for an example that 082 * demonstrates the use of interactive transactions. 083 */ 084@NotMutable() 085@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 086public final class StartInteractiveTransactionExtendedResult 087 extends ExtendedResult 088{ 089 /** 090 * The BER type for the {@code txnID} element of the response. 091 */ 092 private static final byte TYPE_TXN_ID = (byte) 0x80; 093 094 095 096 /** 097 * The BER type for the {@code baseDNs} element of the response. 098 */ 099 private static final byte TYPE_BASE_DNS = (byte) 0xA1; 100 101 102 103 /** 104 * The serial version UID for this serializable class. 105 */ 106 private static final long serialVersionUID = 4010094216900393866L; 107 108 109 110 // The transaction ID returned by the server. 111 private final ASN1OctetString transactionID; 112 113 // The list of base DNs returned by the server, if any. 114 private final List<String> baseDNs; 115 116 117 118 /** 119 * Creates a new start interactive transaction extended result from the 120 * provided extended result. 121 * 122 * @param extendedResult The extended result to be decoded as a start 123 * interactive transaction extended result. It must 124 * not be {@code null}. 125 * 126 * @throws LDAPException If a problem occurs while attempting to decode the 127 * provided extended result as a start interactive 128 * transaction extended result. 129 */ 130 public StartInteractiveTransactionExtendedResult( 131 final ExtendedResult extendedResult) 132 throws LDAPException 133 { 134 super(extendedResult); 135 136 if (! extendedResult.hasValue()) 137 { 138 transactionID = null; 139 baseDNs = null; 140 return; 141 } 142 143 final ASN1Sequence valueSequence; 144 try 145 { 146 final ASN1Element valueElement = 147 ASN1Element.decode(extendedResult.getValue().getValue()); 148 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 149 } 150 catch (final Exception e) 151 { 152 Debug.debugException(e); 153 throw new LDAPException(ResultCode.DECODING_ERROR, 154 ERR_START_INT_TXN_RESULT_VALUE_NOT_SEQUENCE.get(e.getMessage()), e); 155 } 156 157 ASN1OctetString txnID = null; 158 List<String> baseDNList = null; 159 for (final ASN1Element element : valueSequence.elements()) 160 { 161 switch (element.getType()) 162 { 163 case TYPE_TXN_ID: 164 txnID = ASN1OctetString.decodeAsOctetString(element); 165 break; 166 case TYPE_BASE_DNS: 167 try 168 { 169 final ASN1Sequence baseDNsSequence = 170 ASN1Sequence.decodeAsSequence(element); 171 final ArrayList<String> dnList = 172 new ArrayList<>(baseDNsSequence.elements().length); 173 for (final ASN1Element e : baseDNsSequence.elements()) 174 { 175 dnList.add(ASN1OctetString.decodeAsOctetString(e).stringValue()); 176 } 177 baseDNList = Collections.unmodifiableList(dnList); 178 } 179 catch (final Exception e) 180 { 181 Debug.debugException(e); 182 throw new LDAPException(ResultCode.DECODING_ERROR, 183 ERR_START_INT_TXN_RESULT_BASE_DNS_NOT_SEQUENCE.get( 184 e.getMessage()), e); 185 } 186 break; 187 default: 188 throw new LDAPException(ResultCode.DECODING_ERROR, 189 ERR_START_INT_TXN_RESULT_INVALID_ELEMENT.get( 190 StaticUtils.toHex(element.getType()))); 191 } 192 } 193 194 transactionID = txnID; 195 baseDNs = baseDNList; 196 197 if (transactionID == null) 198 { 199 throw new LDAPException(ResultCode.DECODING_ERROR, 200 ERR_START_INT_TXN_RESULT_NO_TXN_ID.get()); 201 } 202 } 203 204 205 206 /** 207 * Creates a new start interactive transaction extended result with the 208 * provided information. 209 * 210 * @param messageID The message ID for the LDAP message that is 211 * associated with this LDAP result. 212 * @param resultCode The result code from the response. 213 * @param diagnosticMessage The diagnostic message from the response, if 214 * available. 215 * @param matchedDN The matched DN from the response, if available. 216 * @param referralURLs The set of referral URLs from the response, if 217 * available. 218 * @param transactionID The transaction ID for this response, if 219 * available. 220 * @param baseDNs The list of base DNs for this response, if 221 * available. 222 * @param responseControls The set of controls from the response, if 223 * available. 224 */ 225 public StartInteractiveTransactionExtendedResult(final int messageID, 226 final ResultCode resultCode, final String diagnosticMessage, 227 final String matchedDN, final String[] referralURLs, 228 final ASN1OctetString transactionID, final List<String> baseDNs, 229 final Control[] responseControls) 230 { 231 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 232 null, encodeValue(transactionID, baseDNs), responseControls); 233 234 this.transactionID = transactionID; 235 236 if (baseDNs == null) 237 { 238 this.baseDNs = null; 239 } 240 else 241 { 242 this.baseDNs = 243 Collections.unmodifiableList(new ArrayList<>(baseDNs)); 244 } 245 } 246 247 248 249 /** 250 * Encodes the provided information into an ASN.1 octet string suitable for 251 * use as the value of this extended result. 252 * 253 * @param transactionID The transaction ID for this response, if available. 254 * @param baseDNs The list of base DNs for this response, if 255 * available. 256 * 257 * @return The ASN.1 octet string containing the encoded value, or 258 * {@code null} if no value should be used. 259 */ 260 private static ASN1OctetString encodeValue( 261 final ASN1OctetString transactionID, 262 final List<String> baseDNs) 263 { 264 if ((transactionID == null) && (baseDNs == null)) 265 { 266 return null; 267 } 268 269 final ArrayList<ASN1Element> elements = new ArrayList<>(2); 270 if (transactionID != null) 271 { 272 elements.add(new ASN1OctetString(TYPE_TXN_ID, transactionID.getValue())); 273 } 274 275 if ((baseDNs != null) && (! baseDNs.isEmpty())) 276 { 277 final ArrayList<ASN1Element> baseDNElements = 278 new ArrayList<>(baseDNs.size()); 279 for (final String s : baseDNs) 280 { 281 baseDNElements.add(new ASN1OctetString(s)); 282 } 283 elements.add(new ASN1Sequence(TYPE_BASE_DNS, baseDNElements)); 284 } 285 286 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 287 } 288 289 290 291 /** 292 * Retrieves the transaction ID for this start interactive transaction 293 * extended result, if available. 294 * 295 * @return The transaction ID for this start interactive transaction extended 296 * result, or {@code null} if none was provided. 297 */ 298 public ASN1OctetString getTransactionID() 299 { 300 return transactionID; 301 } 302 303 304 305 /** 306 * Retrieves the list of base DNs for this start interactive transaction 307 * extended result, if available. 308 * 309 * @return The list of base DNs for this start interactive transaction 310 * extended result, or {@code null} if no base DN list was provided. 311 */ 312 public List<String> getBaseDNs() 313 { 314 return baseDNs; 315 } 316 317 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override() 323 public String getExtendedResultName() 324 { 325 return INFO_EXTENDED_RESULT_NAME_START_INTERACTIVE_TXN.get(); 326 } 327 328 329 330 /** 331 * {@inheritDoc} 332 */ 333 @Override() 334 public void toString(final StringBuilder buffer) 335 { 336 buffer.append("StartInteractiveTransactionExtendedResult(resultCode="); 337 buffer.append(getResultCode()); 338 339 final int messageID = getMessageID(); 340 if (messageID >= 0) 341 { 342 buffer.append(", messageID="); 343 buffer.append(messageID); 344 } 345 346 if (transactionID != null) 347 { 348 buffer.append(", transactionID='"); 349 buffer.append(transactionID.stringValue()); 350 buffer.append('\''); 351 } 352 353 if (baseDNs != null) 354 { 355 buffer.append(", baseDNs={"); 356 for (int i=0; i < baseDNs.size(); i++) 357 { 358 if (i > 0) 359 { 360 buffer.append(", "); 361 } 362 363 buffer.append('\''); 364 buffer.append(baseDNs.get(i)); 365 buffer.append('\''); 366 } 367 buffer.append('}'); 368 } 369 370 final String diagnosticMessage = getDiagnosticMessage(); 371 if (diagnosticMessage != null) 372 { 373 buffer.append(", diagnosticMessage='"); 374 buffer.append(diagnosticMessage); 375 buffer.append('\''); 376 } 377 378 final String matchedDN = getMatchedDN(); 379 if (matchedDN != null) 380 { 381 buffer.append(", matchedDN='"); 382 buffer.append(matchedDN); 383 buffer.append('\''); 384 } 385 386 final String[] referralURLs = getReferralURLs(); 387 if (referralURLs.length > 0) 388 { 389 buffer.append(", referralURLs={"); 390 for (int i=0; i < referralURLs.length; i++) 391 { 392 if (i > 0) 393 { 394 buffer.append(", "); 395 } 396 397 buffer.append('\''); 398 buffer.append(referralURLs[i]); 399 buffer.append('\''); 400 } 401 buffer.append('}'); 402 } 403 404 final Control[] responseControls = getResponseControls(); 405 if (responseControls.length > 0) 406 { 407 buffer.append(", responseControls={"); 408 for (int i=0; i < responseControls.length; i++) 409 { 410 if (i > 0) 411 { 412 buffer.append(", "); 413 } 414 415 buffer.append(responseControls[i]); 416 } 417 buffer.append('}'); 418 } 419 420 buffer.append(')'); 421 } 422}