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) 2015-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.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.List; 043 044import com.unboundid.asn1.ASN1Boolean; 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.DecodeableControl; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.LDAPResult; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.ldap.sdk.unboundidds.extensions. 054 StartInteractiveTransactionExtendedRequest; 055import com.unboundid.util.NotMutable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 061 062 063 064/** 065 * This class defines an interactive transaction specification response control, 066 * which will be included in the server's response to an operation that included 067 * the {@link InteractiveTransactionSpecificationRequestControl}. 068 * <BR> 069 * <BLOCKQUOTE> 070 * <B>NOTE:</B> This class, and other classes within the 071 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 072 * supported for use against Ping Identity, UnboundID, and 073 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 074 * for proprietary functionality or for external specifications that are not 075 * considered stable or mature enough to be guaranteed to work in an 076 * interoperable way with other types of LDAP servers. 077 * </BLOCKQUOTE> 078 * <BR> 079 * It provides information about the state of the transaction, which may 080 * include: 081 * <UL> 082 * <LI><CODE>transactionValid</CODE> -- Indicates whether the transaction is 083 * still valid in the server. This should be checked if the associated 084 * operation did not complete successfully.</LI> 085 * <LI><CODE>baseDNs</CODE> -- This may specify the set of base DNs below 086 * which the client is allowed to request operations as part of this 087 * transaction. It may be absent if there are no restrictions on which 088 * base DNs may be used, or if it has not changed since the last 089 * response within this transaction.</LI> 090 * </UL> 091 * See the documentation in the 092 * {@link StartInteractiveTransactionExtendedRequest} class for an example of 093 * processing interactive transactions. 094 */ 095@NotMutable() 096@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 097public final class InteractiveTransactionSpecificationResponseControl 098 extends Control 099 implements DecodeableControl 100{ 101 /** 102 * The OID (1.3.6.1.4.1.30221.2.5.4) for the interactive transaction 103 * specification response control. 104 */ 105 public static final String 106 INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID = 107 "1.3.6.1.4.1.30221.2.5.4"; 108 109 110 111 /** 112 * The BER type for the {@code transactionValid} element of the control value. 113 */ 114 private static final byte TYPE_TXN_VALID = (byte) 0x80; 115 116 117 118 /** 119 * The BER type for the {@code baseDNs} element of the control value. 120 */ 121 private static final byte TYPE_BASE_DNS = (byte) 0xA1; 122 123 124 125 /** 126 * The serial version UID for this serializable class. 127 */ 128 private static final long serialVersionUID = -4323085263241417543L; 129 130 131 132 // The flag that indicates whether the associated transaction is still valid. 133 private final boolean transactionValid; 134 135 // The set of base DNs that may be targeted by this transaction. 136 private final List<String> baseDNs; 137 138 139 140 /** 141 * Creates a new empty control instance that is intended to be used only for 142 * decoding controls via the {@code DecodeableControl} interface. 143 */ 144 InteractiveTransactionSpecificationResponseControl() 145 { 146 transactionValid = false; 147 baseDNs = null; 148 } 149 150 151 152 /** 153 * Creates a new interactive transaction specification response control with 154 * the provided information. It will not be marked critical. 155 * 156 * @param transactionValid Indicates whether the associated transaction is 157 * still valid. 158 * @param baseDNs The set of base DNs that may be targeted over the 159 * course of the transaction. It may be 160 * {@code null} if there are no restrictions or the 161 * set of restrictions has not changed since the 162 * last response. 163 */ 164 public InteractiveTransactionSpecificationResponseControl( 165 final boolean transactionValid, final List<String> baseDNs) 166 { 167 super(INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID, false, 168 encodeValue(transactionValid, baseDNs)); 169 170 this.transactionValid = transactionValid; 171 172 if (baseDNs == null) 173 { 174 this.baseDNs = null; 175 } 176 else 177 { 178 this.baseDNs = 179 Collections.unmodifiableList(new ArrayList<>(baseDNs)); 180 } 181 } 182 183 184 185 /** 186 * Creates a new interactive transaction specification response control with 187 * the provided information. 188 * 189 * @param oid The OID for the control. 190 * @param isCritical Indicates whether the control should be marked 191 * critical. 192 * @param value The encoded value for the control. This may be 193 * {@code null} if no value was provided. 194 * 195 * @throws LDAPException If the provided control cannot be decoded as an 196 * interactive transaction specification response 197 * control. 198 */ 199 public InteractiveTransactionSpecificationResponseControl(final String oid, 200 final boolean isCritical, final ASN1OctetString value) 201 throws LDAPException 202 { 203 super(oid, isCritical, value); 204 205 if (value == null) 206 { 207 throw new LDAPException(ResultCode.DECODING_ERROR, 208 ERR_INT_TXN_RESPONSE_NO_VALUE.get()); 209 } 210 211 final ASN1Element[] elements; 212 try 213 { 214 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 215 elements = ASN1Sequence.decodeAsSequence(valueElement).elements(); 216 } 217 catch (final Exception e) 218 { 219 throw new LDAPException(ResultCode.DECODING_ERROR, 220 ERR_INT_TXN_RESPONSE_VALUE_NOT_SEQUENCE.get( 221 e.getMessage()), e); 222 } 223 224 Boolean isValid = null; 225 List<String> baseDNList = null; 226 227 for (final ASN1Element element : elements) 228 { 229 switch (element.getType()) 230 { 231 case TYPE_TXN_VALID: 232 try 233 { 234 isValid = ASN1Boolean.decodeAsBoolean(element).booleanValue(); 235 } 236 catch (final Exception e) 237 { 238 throw new LDAPException(ResultCode.DECODING_ERROR, 239 ERR_INT_TXN_RESPONSE_TXN_VALID_NOT_BOOLEAN.get(e.getMessage()), 240 e); 241 } 242 break; 243 case TYPE_BASE_DNS: 244 try 245 { 246 final ASN1Sequence s = ASN1Sequence.decodeAsSequence(element); 247 baseDNList = new ArrayList<>(s.elements().length); 248 for (final ASN1Element e : s.elements()) 249 { 250 baseDNList.add( 251 ASN1OctetString.decodeAsOctetString(e).stringValue()); 252 } 253 } 254 catch (final Exception e) 255 { 256 throw new LDAPException(ResultCode.DECODING_ERROR, 257 ERR_INT_TXN_RESPONSE_BASE_DNS_NOT_SEQUENCE.get(e.getMessage()), 258 e); 259 } 260 break; 261 default: 262 throw new LDAPException(ResultCode.DECODING_ERROR, 263 ERR_INT_TXN_RESPONSE_INVALID_ELEMENT_TYPE.get( 264 StaticUtils.toHex(element.getType()))); 265 } 266 } 267 268 if (isValid == null) 269 { 270 throw new LDAPException(ResultCode.DECODING_ERROR, 271 ERR_INT_TXN_RESPONSE_NO_TXN_VALID.get()); 272 } 273 274 transactionValid = isValid; 275 276 if (baseDNList == null) 277 { 278 baseDNs = null; 279 } 280 else 281 { 282 baseDNs = Collections.unmodifiableList(baseDNList); 283 } 284 } 285 286 287 288 /** 289 * Encodes the provided information into an ASN.1 octet string suitable for 290 * use as the value of this control. 291 * 292 * @param transactionValid Indicates whether the associated transaction is 293 * still valid. 294 * @param baseDNs The set of base DNs that may be targeted over the 295 * course of the transaction. It may be 296 * {@code null} if there are no restrictions or the 297 * set of restrictions has not changed since the 298 * last response. 299 * 300 * @return The ASN1 octet string that may be used as the control value. 301 */ 302 private static ASN1OctetString encodeValue(final boolean transactionValid, 303 final List<String> baseDNs) 304 { 305 final ASN1Element[] elements; 306 if (baseDNs == null) 307 { 308 elements = new ASN1Element[] 309 { 310 new ASN1Boolean(TYPE_TXN_VALID, transactionValid) 311 }; 312 } 313 else 314 { 315 final ASN1Element[] baseDNElements = new ASN1Element[baseDNs.size()]; 316 for (int i=0; i < baseDNElements.length; i++) 317 { 318 baseDNElements[i] = new ASN1OctetString(baseDNs.get(i)); 319 } 320 321 elements = new ASN1Element[] 322 { 323 new ASN1Boolean(TYPE_TXN_VALID, transactionValid), 324 new ASN1Sequence(TYPE_BASE_DNS, baseDNElements) 325 }; 326 } 327 328 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 329 } 330 331 332 333 /** 334 * {@inheritDoc} 335 */ 336 @Override() 337 public InteractiveTransactionSpecificationResponseControl decodeControl( 338 final String oid, final boolean isCritical, 339 final ASN1OctetString value) 340 throws LDAPException 341 { 342 return new InteractiveTransactionSpecificationResponseControl(oid, 343 isCritical, value); 344 } 345 346 347 348 /** 349 * Extracts an interactive transaction specification response control from the 350 * provided result. 351 * 352 * @param result The result from which to retrieve the interactive 353 * transaction specification response control. 354 * 355 * @return The interactive transaction specification response control 356 * contained in the provided result, or {@code null} if the result 357 * did not contain an interactive transaction specification response 358 * control. 359 * 360 * @throws LDAPException If a problem is encountered while attempting to 361 * decode the interactive transaction specification 362 * response control contained in the provided result. 363 */ 364 public static InteractiveTransactionSpecificationResponseControl 365 get(final LDAPResult result) 366 throws LDAPException 367 { 368 final Control c = result.getResponseControl( 369 INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID); 370 if (c == null) 371 { 372 return null; 373 } 374 375 if (c instanceof InteractiveTransactionSpecificationResponseControl) 376 { 377 return (InteractiveTransactionSpecificationResponseControl) c; 378 } 379 else 380 { 381 return new InteractiveTransactionSpecificationResponseControl(c.getOID(), 382 c.isCritical(), c.getValue()); 383 } 384 } 385 386 387 388 /** 389 * Indicates whether the associated transaction is still valid on the server. 390 * 391 * @return {@code true} if the associated transaction is still valid on the 392 * server and may be used for future operations, or {@code false} if 393 * the transaction has been aborted and may no longer be used. 394 */ 395 public boolean transactionValid() 396 { 397 return transactionValid; 398 } 399 400 401 402 /** 403 * Retrieves the set of base DNs below which operations which are part of the 404 * transaction may be performed. 405 * 406 * @return The set of base DNs below which operations may be performed as 407 * part of the transaction, or {@code null} if there are no 408 * restrictions or if the set of restrictions has not changed since 409 * the last response. 410 */ 411 public List<String> getBaseDNs() 412 { 413 return baseDNs; 414 } 415 416 417 418 /** 419 * {@inheritDoc} 420 */ 421 @Override() 422 public String getControlName() 423 { 424 return INFO_CONTROL_NAME_INTERACTIVE_TXN_RESPONSE.get(); 425 } 426 427 428 429 /** 430 * {@inheritDoc} 431 */ 432 @Override() 433 public void toString(final StringBuilder buffer) 434 { 435 buffer.append("InteractiveTransactionSpecificationResponseControl("); 436 buffer.append("transactionValid="); 437 buffer.append(transactionValid); 438 buffer.append(", baseDNs="); 439 if (baseDNs == null) 440 { 441 buffer.append("null"); 442 } 443 else 444 { 445 buffer.append('{'); 446 for (int i=0; i < baseDNs.size(); i++) 447 { 448 if (i > 0) 449 { 450 buffer.append(", "); 451 } 452 453 buffer.append('\''); 454 buffer.append(baseDNs.get(i)); 455 buffer.append('\''); 456 } 457 buffer.append('}'); 458 } 459 460 buffer.append(", isCritical="); 461 buffer.append(isCritical()); 462 buffer.append(')'); 463 } 464}