001/* 002 * Copyright 2008-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.extensions; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.List; 043 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.asn1.ASN1Sequence; 047import com.unboundid.ldap.sdk.Control; 048import com.unboundid.ldap.sdk.ExtendedResult; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.NotNull; 054import com.unboundid.util.Nullable; 055import com.unboundid.util.StaticUtils; 056import com.unboundid.util.ThreadSafety; 057import com.unboundid.util.ThreadSafetyLevel; 058 059import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 060 061 062 063/** 064 * <BLOCKQUOTE> 065 * <B>NOTE:</B> The use of interactive transactions is discouraged because it 066 * can create conditions which are prone to deadlocks between operations that 067 * may result in the cancellation of one or both operations. It is strongly 068 * recommended that standard LDAP transactions (which may be started using a 069 * {@link com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest}) 070 * or a multi-update extended operation be used instead. Although they cannot 071 * include arbitrary read operations, LDAP transactions and multi-update 072 * operations may be used in conjunction with the 073 * {@link com.unboundid.ldap.sdk.controls.AssertionRequestControl}, 074 * {@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}, and 075 * {@link com.unboundid.ldap.sdk.controls.PostReadRequestControl} to 076 * incorporate some read capability into a transaction, and in conjunction 077 * with the {@link com.unboundid.ldap.sdk.ModificationType#INCREMENT} 078 * modification type to increment integer values without the need to know the 079 * precise value before or after the operation (although the pre-read and/or 080 * post-read controls may be used to determine that). 081 * </BLOCKQUOTE> 082 * This class implements a data structure for storing the information from an 083 * extended result for the start interactive transaction extended request. It 084 * is able to decode a generic extended result to extract the transaction ID and 085 * base DNs that it may contain, if the operation was successful. 086 * <BR> 087 * <BLOCKQUOTE> 088 * <B>NOTE:</B> This class, and other classes within the 089 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 090 * supported for use against Ping Identity, UnboundID, and 091 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 092 * for proprietary functionality or for external specifications that are not 093 * considered stable or mature enough to be guaranteed to work in an 094 * interoperable way with other types of LDAP servers. 095 * </BLOCKQUOTE> 096 * <BR> 097 * See the documentation for the 098 * {@link StartInteractiveTransactionExtendedRequest} class for an example that 099 * demonstrates the use of interactive transactions. 100 */ 101@NotMutable() 102@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 103public final class StartInteractiveTransactionExtendedResult 104 extends ExtendedResult 105{ 106 /** 107 * The BER type for the {@code txnID} element of the response. 108 */ 109 private static final byte TYPE_TXN_ID = (byte) 0x80; 110 111 112 113 /** 114 * The BER type for the {@code baseDNs} element of the response. 115 */ 116 private static final byte TYPE_BASE_DNS = (byte) 0xA1; 117 118 119 120 /** 121 * The serial version UID for this serializable class. 122 */ 123 private static final long serialVersionUID = 4010094216900393866L; 124 125 126 127 // The transaction ID returned by the server. 128 @Nullable private final ASN1OctetString transactionID; 129 130 // The list of base DNs returned by the server, if any. 131 @Nullable private final List<String> baseDNs; 132 133 134 135 /** 136 * Creates a new start interactive transaction extended result from the 137 * provided extended result. 138 * 139 * @param extendedResult The extended result to be decoded as a start 140 * interactive transaction extended result. It must 141 * not be {@code null}. 142 * 143 * @throws LDAPException If a problem occurs while attempting to decode the 144 * provided extended result as a start interactive 145 * transaction extended result. 146 */ 147 public StartInteractiveTransactionExtendedResult( 148 @NotNull final ExtendedResult extendedResult) 149 throws LDAPException 150 { 151 super(extendedResult); 152 153 if (! extendedResult.hasValue()) 154 { 155 transactionID = null; 156 baseDNs = null; 157 return; 158 } 159 160 final ASN1Sequence valueSequence; 161 try 162 { 163 final ASN1Element valueElement = 164 ASN1Element.decode(extendedResult.getValue().getValue()); 165 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 166 } 167 catch (final Exception e) 168 { 169 Debug.debugException(e); 170 throw new LDAPException(ResultCode.DECODING_ERROR, 171 ERR_START_INT_TXN_RESULT_VALUE_NOT_SEQUENCE.get(e.getMessage()), e); 172 } 173 174 ASN1OctetString txnID = null; 175 List<String> baseDNList = null; 176 for (final ASN1Element element : valueSequence.elements()) 177 { 178 switch (element.getType()) 179 { 180 case TYPE_TXN_ID: 181 txnID = ASN1OctetString.decodeAsOctetString(element); 182 break; 183 case TYPE_BASE_DNS: 184 try 185 { 186 final ASN1Sequence baseDNsSequence = 187 ASN1Sequence.decodeAsSequence(element); 188 final ArrayList<String> dnList = 189 new ArrayList<>(baseDNsSequence.elements().length); 190 for (final ASN1Element e : baseDNsSequence.elements()) 191 { 192 dnList.add(ASN1OctetString.decodeAsOctetString(e).stringValue()); 193 } 194 baseDNList = Collections.unmodifiableList(dnList); 195 } 196 catch (final Exception e) 197 { 198 Debug.debugException(e); 199 throw new LDAPException(ResultCode.DECODING_ERROR, 200 ERR_START_INT_TXN_RESULT_BASE_DNS_NOT_SEQUENCE.get( 201 e.getMessage()), e); 202 } 203 break; 204 default: 205 throw new LDAPException(ResultCode.DECODING_ERROR, 206 ERR_START_INT_TXN_RESULT_INVALID_ELEMENT.get( 207 StaticUtils.toHex(element.getType()))); 208 } 209 } 210 211 transactionID = txnID; 212 baseDNs = baseDNList; 213 214 if (transactionID == null) 215 { 216 throw new LDAPException(ResultCode.DECODING_ERROR, 217 ERR_START_INT_TXN_RESULT_NO_TXN_ID.get()); 218 } 219 } 220 221 222 223 /** 224 * Creates a new start interactive transaction extended result with the 225 * provided information. 226 * 227 * @param messageID The message ID for the LDAP message that is 228 * associated with this LDAP result. 229 * @param resultCode The result code from the response. 230 * @param diagnosticMessage The diagnostic message from the response, if 231 * available. 232 * @param matchedDN The matched DN from the response, if available. 233 * @param referralURLs The set of referral URLs from the response, if 234 * available. 235 * @param transactionID The transaction ID for this response, if 236 * available. 237 * @param baseDNs The list of base DNs for this response, if 238 * available. 239 * @param responseControls The set of controls from the response, if 240 * available. 241 */ 242 public StartInteractiveTransactionExtendedResult(final int messageID, 243 @NotNull final ResultCode resultCode, 244 @Nullable final String diagnosticMessage, 245 @Nullable final String matchedDN, 246 @Nullable final String[] referralURLs, 247 @Nullable final ASN1OctetString transactionID, 248 @Nullable final List<String> baseDNs, 249 @Nullable final Control[] responseControls) 250 { 251 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 252 null, encodeValue(transactionID, baseDNs), responseControls); 253 254 this.transactionID = transactionID; 255 256 if (baseDNs == null) 257 { 258 this.baseDNs = null; 259 } 260 else 261 { 262 this.baseDNs = 263 Collections.unmodifiableList(new ArrayList<>(baseDNs)); 264 } 265 } 266 267 268 269 /** 270 * Encodes the provided information into an ASN.1 octet string suitable for 271 * use as the value of this extended result. 272 * 273 * @param transactionID The transaction ID for this response, if available. 274 * @param baseDNs The list of base DNs for this response, if 275 * available. 276 * 277 * @return The ASN.1 octet string containing the encoded value, or 278 * {@code null} if no value should be used. 279 */ 280 @Nullable() 281 private static ASN1OctetString encodeValue( 282 @Nullable final ASN1OctetString transactionID, 283 @Nullable final List<String> baseDNs) 284 { 285 if ((transactionID == null) && (baseDNs == null)) 286 { 287 return null; 288 } 289 290 final ArrayList<ASN1Element> elements = new ArrayList<>(2); 291 if (transactionID != null) 292 { 293 elements.add(new ASN1OctetString(TYPE_TXN_ID, transactionID.getValue())); 294 } 295 296 if ((baseDNs != null) && (! baseDNs.isEmpty())) 297 { 298 final ArrayList<ASN1Element> baseDNElements = 299 new ArrayList<>(baseDNs.size()); 300 for (final String s : baseDNs) 301 { 302 baseDNElements.add(new ASN1OctetString(s)); 303 } 304 elements.add(new ASN1Sequence(TYPE_BASE_DNS, baseDNElements)); 305 } 306 307 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 308 } 309 310 311 312 /** 313 * Retrieves the transaction ID for this start interactive transaction 314 * extended result, if available. 315 * 316 * @return The transaction ID for this start interactive transaction extended 317 * result, or {@code null} if none was provided. 318 */ 319 @Nullable() 320 public ASN1OctetString getTransactionID() 321 { 322 return transactionID; 323 } 324 325 326 327 /** 328 * Retrieves the list of base DNs for this start interactive transaction 329 * extended result, if available. 330 * 331 * @return The list of base DNs for this start interactive transaction 332 * extended result, or {@code null} if no base DN list was provided. 333 */ 334 @Nullable() 335 public List<String> getBaseDNs() 336 { 337 return baseDNs; 338 } 339 340 341 342 /** 343 * {@inheritDoc} 344 */ 345 @Override() 346 @NotNull() 347 public String getExtendedResultName() 348 { 349 return INFO_EXTENDED_RESULT_NAME_START_INTERACTIVE_TXN.get(); 350 } 351 352 353 354 /** 355 * {@inheritDoc} 356 */ 357 @Override() 358 public void toString(@NotNull final StringBuilder buffer) 359 { 360 buffer.append("StartInteractiveTransactionExtendedResult(resultCode="); 361 buffer.append(getResultCode()); 362 363 final int messageID = getMessageID(); 364 if (messageID >= 0) 365 { 366 buffer.append(", messageID="); 367 buffer.append(messageID); 368 } 369 370 if (transactionID != null) 371 { 372 buffer.append(", transactionID='"); 373 buffer.append(transactionID.stringValue()); 374 buffer.append('\''); 375 } 376 377 if (baseDNs != null) 378 { 379 buffer.append(", baseDNs={"); 380 for (int i=0; i < baseDNs.size(); i++) 381 { 382 if (i > 0) 383 { 384 buffer.append(", "); 385 } 386 387 buffer.append('\''); 388 buffer.append(baseDNs.get(i)); 389 buffer.append('\''); 390 } 391 buffer.append('}'); 392 } 393 394 final String diagnosticMessage = getDiagnosticMessage(); 395 if (diagnosticMessage != null) 396 { 397 buffer.append(", diagnosticMessage='"); 398 buffer.append(diagnosticMessage); 399 buffer.append('\''); 400 } 401 402 final String matchedDN = getMatchedDN(); 403 if (matchedDN != null) 404 { 405 buffer.append(", matchedDN='"); 406 buffer.append(matchedDN); 407 buffer.append('\''); 408 } 409 410 final String[] referralURLs = getReferralURLs(); 411 if (referralURLs.length > 0) 412 { 413 buffer.append(", referralURLs={"); 414 for (int i=0; i < referralURLs.length; i++) 415 { 416 if (i > 0) 417 { 418 buffer.append(", "); 419 } 420 421 buffer.append('\''); 422 buffer.append(referralURLs[i]); 423 buffer.append('\''); 424 } 425 buffer.append('}'); 426 } 427 428 final Control[] responseControls = getResponseControls(); 429 if (responseControls.length > 0) 430 { 431 buffer.append(", responseControls={"); 432 for (int i=0; i < responseControls.length; i++) 433 { 434 if (i > 0) 435 { 436 buffer.append(", "); 437 } 438 439 buffer.append(responseControls[i]); 440 } 441 buffer.append('}'); 442 } 443 444 buffer.append(')'); 445 } 446}