001/* 002 * Copyright 2010-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-2014 UnboundID Corp. 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.extensions; 022 023 024import java.util.ArrayList; 025import java.util.Map; 026import java.util.TreeMap; 027 028import com.unboundid.asn1.ASN1Constants; 029import com.unboundid.asn1.ASN1Element; 030import com.unboundid.asn1.ASN1Exception; 031import com.unboundid.asn1.ASN1Integer; 032import com.unboundid.asn1.ASN1OctetString; 033import com.unboundid.asn1.ASN1Sequence; 034import com.unboundid.ldap.sdk.Control; 035import com.unboundid.ldap.sdk.ExtendedResult; 036import com.unboundid.ldap.sdk.LDAPException; 037import com.unboundid.ldap.sdk.ResultCode; 038import com.unboundid.util.NotMutable; 039import com.unboundid.util.ThreadSafety; 040import com.unboundid.util.ThreadSafetyLevel; 041 042import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*; 043import static com.unboundid.util.Debug.*; 044import static com.unboundid.util.StaticUtils.*; 045 046 047 048/** 049 * This class provides an implementation of the end transaction extended result 050 * as defined in 051 * <A HREF="http://www.ietf.org/rfc/rfc5805.txt">RFC 5805</A>. It is able to 052 * decode a generic extended result to extract the appropriate response 053 * information. 054 * <BR><BR> 055 * See the documentation for the {@link StartTransactionExtendedRequest} class 056 * for an example of performing a transaction. 057 */ 058@NotMutable() 059@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 060public final class EndTransactionExtendedResult 061 extends ExtendedResult 062{ 063 /** 064 * The serial version UID for this serializable class. 065 */ 066 private static final long serialVersionUID = 1514265185948328221L; 067 068 069 070 // The message ID for the operation that failed, if applicable. 071 private final int failedOpMessageID; 072 073 // A mapping of the response controls for the operations performed as part of 074 // the transaction. 075 private final TreeMap<Integer,Control[]> opResponseControls; 076 077 078 079 /** 080 * Creates a new end transaction extended result from the provided extended 081 * result. 082 * 083 * @param extendedResult The extended result to be decoded as an end 084 * transaction extended result. It must not be 085 * {@code null}. 086 * 087 * @throws LDAPException If a problem occurs while attempting to decode the 088 * provided extended result as an end transaction 089 * extended result. 090 */ 091 public EndTransactionExtendedResult(final ExtendedResult extendedResult) 092 throws LDAPException 093 { 094 super(extendedResult); 095 096 opResponseControls = new TreeMap<Integer,Control[]>(); 097 098 final ASN1OctetString value = extendedResult.getValue(); 099 if (value == null) 100 { 101 failedOpMessageID = -1; 102 return; 103 } 104 105 final ASN1Sequence valueSequence; 106 try 107 { 108 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 109 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 110 } 111 catch (final ASN1Exception ae) 112 { 113 debugException(ae); 114 throw new LDAPException(ResultCode.DECODING_ERROR, 115 ERR_END_TXN_RESPONSE_VALUE_NOT_SEQUENCE.get(ae.getMessage()), ae); 116 } 117 118 final ASN1Element[] valueElements = valueSequence.elements(); 119 if (valueElements.length == 0) 120 { 121 failedOpMessageID = -1; 122 return; 123 } 124 else if (valueElements.length > 2) 125 { 126 throw new LDAPException(ResultCode.DECODING_ERROR, 127 ERR_END_TXN_RESPONSE_INVALID_ELEMENT_COUNT.get( 128 valueElements.length)); 129 } 130 131 int msgID = -1; 132 for (final ASN1Element e : valueElements) 133 { 134 if (e.getType() == ASN1Constants.UNIVERSAL_INTEGER_TYPE) 135 { 136 try 137 { 138 msgID = ASN1Integer.decodeAsInteger(e).intValue(); 139 } 140 catch (final ASN1Exception ae) 141 { 142 debugException(ae); 143 throw new LDAPException(ResultCode.DECODING_ERROR, 144 ERR_END_TXN_RESPONSE_CANNOT_DECODE_MSGID.get(ae), ae); 145 } 146 } 147 else if (e.getType() == ASN1Constants.UNIVERSAL_SEQUENCE_TYPE) 148 { 149 decodeOpControls(e, opResponseControls); 150 } 151 else 152 { 153 throw new LDAPException(ResultCode.DECODING_ERROR, 154 ERR_END_TXN_RESPONSE_INVALID_TYPE.get(toHex(e.getType()))); 155 } 156 } 157 158 failedOpMessageID = msgID; 159 } 160 161 162 163 /** 164 * Creates a new end transaction extended result with the provided 165 * information. 166 * 167 * @param messageID The message ID for the LDAP message that is 168 * associated with this LDAP result. 169 * @param resultCode The result code from the response. 170 * @param diagnosticMessage The diagnostic message from the response, if 171 * available. 172 * @param matchedDN The matched DN from the response, if available. 173 * @param referralURLs The set of referral URLs from the response, if 174 * available. 175 * @param failedOpMessageID The message ID for the operation that failed, 176 * or {@code null} if there was no failure. 177 * @param opResponseControls A map containing the response controls for each 178 * operation, indexed by message ID. It may be 179 * {@code null} if there were no response 180 * controls. 181 * @param responseControls The set of controls from the response, if 182 * available. 183 */ 184 public EndTransactionExtendedResult(final int messageID, 185 final ResultCode resultCode, final String diagnosticMessage, 186 final String matchedDN, final String[] referralURLs, 187 final Integer failedOpMessageID, 188 final Map<Integer,Control[]> opResponseControls, 189 final Control[] responseControls) 190 { 191 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 192 null, encodeValue(failedOpMessageID, opResponseControls), 193 responseControls); 194 195 if ((failedOpMessageID == null) || (failedOpMessageID <= 0)) 196 { 197 this.failedOpMessageID = -1; 198 } 199 else 200 { 201 this.failedOpMessageID = failedOpMessageID; 202 } 203 204 if (opResponseControls == null) 205 { 206 this.opResponseControls = new TreeMap<Integer,Control[]>(); 207 } 208 else 209 { 210 this.opResponseControls = 211 new TreeMap<Integer,Control[]>(opResponseControls); 212 } 213 } 214 215 216 217 /** 218 * Decodes the provided ASN.1 element as an update controls sequence. Each 219 * element of the sequence should itself be a sequence containing the message 220 * ID associated with the operation in which the control was returned and a 221 * sequence of the controls included in the response for that operation. 222 * 223 * @param element The ASN.1 element to be decoded. 224 * @param controlMap The map into which to place the decoded controls. 225 * 226 * @throws LDAPException If a problem occurs while attempting to decode the 227 * contents of the provided ASN.1 element. 228 */ 229 private static void decodeOpControls(final ASN1Element element, 230 final Map<Integer,Control[]> controlMap) 231 throws LDAPException 232 { 233 final ASN1Sequence ctlsSequence; 234 try 235 { 236 ctlsSequence = ASN1Sequence.decodeAsSequence(element); 237 } 238 catch (final ASN1Exception ae) 239 { 240 debugException(ae); 241 throw new LDAPException(ResultCode.DECODING_ERROR, 242 ERR_END_TXN_RESPONSE_CONTROLS_NOT_SEQUENCE.get(ae), ae); 243 } 244 245 for (final ASN1Element e : ctlsSequence.elements()) 246 { 247 final ASN1Sequence ctlSequence; 248 try 249 { 250 ctlSequence = ASN1Sequence.decodeAsSequence(e); 251 } 252 catch (final ASN1Exception ae) 253 { 254 debugException(ae); 255 throw new LDAPException(ResultCode.DECODING_ERROR, 256 ERR_END_TXN_RESPONSE_CONTROL_NOT_SEQUENCE.get(ae), ae); 257 } 258 259 final ASN1Element[] ctlSequenceElements = ctlSequence.elements(); 260 if (ctlSequenceElements.length != 2) 261 { 262 throw new LDAPException(ResultCode.DECODING_ERROR, 263 ERR_END_TXN_RESPONSE_CONTROL_INVALID_ELEMENT_COUNT.get( 264 ctlSequenceElements.length)); 265 } 266 267 final int msgID; 268 try 269 { 270 msgID = ASN1Integer.decodeAsInteger(ctlSequenceElements[0]).intValue(); 271 } 272 catch (final ASN1Exception ae) 273 { 274 debugException(ae); 275 throw new LDAPException(ResultCode.DECODING_ERROR, 276 ERR_END_TXN_RESPONSE_CONTROL_MSGID_NOT_INT.get(ae), ae); 277 } 278 279 final ASN1Sequence controlsSequence; 280 try 281 { 282 controlsSequence = 283 ASN1Sequence.decodeAsSequence(ctlSequenceElements[1]); 284 } 285 catch (final ASN1Exception ae) 286 { 287 debugException(ae); 288 throw new LDAPException(ResultCode.DECODING_ERROR, 289 ERR_END_TXN_RESPONSE_CONTROLS_ELEMENT_NOT_SEQUENCE.get(ae), ae); 290 } 291 292 final Control[] controls = Control.decodeControls(controlsSequence); 293 if (controls.length == 0) 294 { 295 continue; 296 } 297 298 controlMap.put(msgID, controls); 299 } 300 } 301 302 303 304 /** 305 * Encodes the provided information into an appropriate value for this 306 * control. 307 * 308 * @param failedOpMessageID The message ID for the operation that failed, 309 * or {@code null} if there was no failure. 310 * @param opResponseControls A map containing the response controls for each 311 * operation, indexed by message ID. It may be 312 * {@code null} if there were no response 313 * controls. 314 * 315 * @return An ASN.1 octet string containing the encoded value for this 316 * control, or {@code null} if there should not be a value. 317 */ 318 private static ASN1OctetString encodeValue(final Integer failedOpMessageID, 319 final Map<Integer,Control[]> opResponseControls) 320 { 321 if ((failedOpMessageID == null) && (opResponseControls == null)) 322 { 323 return null; 324 } 325 326 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2); 327 if (failedOpMessageID != null) 328 { 329 elements.add(new ASN1Integer(failedOpMessageID)); 330 } 331 332 if ((opResponseControls != null) && (! opResponseControls.isEmpty())) 333 { 334 final ArrayList<ASN1Element> controlElements = 335 new ArrayList<ASN1Element>(); 336 for (final Map.Entry<Integer,Control[]> e : opResponseControls.entrySet()) 337 { 338 final ASN1Element[] ctlElements = 339 { 340 new ASN1Integer(e.getKey()), 341 Control.encodeControls(e.getValue()) 342 }; 343 controlElements.add(new ASN1Sequence(ctlElements)); 344 } 345 346 elements.add(new ASN1Sequence(controlElements)); 347 } 348 349 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 350 } 351 352 353 354 355 /** 356 * Retrieves the message ID of the operation that caused the transaction 357 * processing to fail, if applicable. 358 * 359 * @return The message ID of the operation that caused the transaction 360 * processing to fail, or -1 if no message ID was included in the 361 * end transaction response. 362 */ 363 public int getFailedOpMessageID() 364 { 365 return failedOpMessageID; 366 } 367 368 369 370 /** 371 * Retrieves the set of response controls returned by the operations 372 * processed as part of the transaction. The value returned will contain a 373 * mapping between the message ID of the associated request message and a list 374 * of the response controls for that operation. 375 * 376 * @return The set of response controls returned by the operations processed 377 * as part of the transaction. It may be an empty map if none of the 378 * operations had any response controls. 379 */ 380 public Map<Integer,Control[]> getOperationResponseControls() 381 { 382 return opResponseControls; 383 } 384 385 386 387 /** 388 * Retrieves the set of response controls returned by the specified operation 389 * processed as part of the transaction. 390 * 391 * @param messageID The message ID of the operation for which to retrieve 392 * the response controls. 393 * 394 * @return The response controls for the specified operation, or 395 * {@code null} if there were no controls returned for the specified 396 * operation. 397 */ 398 public Control[] getOperationResponseControls(final int messageID) 399 { 400 return opResponseControls.get(messageID); 401 } 402 403 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override() 409 public String getExtendedResultName() 410 { 411 return INFO_EXTENDED_RESULT_NAME_END_TXN.get(); 412 } 413 414 415 416 /** 417 * Appends a string representation of this extended result to the provided 418 * buffer. 419 * 420 * @param buffer The buffer to which a string representation of this 421 * extended result will be appended. 422 */ 423 @Override() 424 public void toString(final StringBuilder buffer) 425 { 426 buffer.append("EndTransactionExtendedResult(resultCode="); 427 buffer.append(getResultCode()); 428 429 final int messageID = getMessageID(); 430 if (messageID >= 0) 431 { 432 buffer.append(", messageID="); 433 buffer.append(messageID); 434 } 435 436 if (failedOpMessageID > 0) 437 { 438 buffer.append(", failedOpMessageID="); 439 buffer.append(failedOpMessageID); 440 } 441 442 if (! opResponseControls.isEmpty()) 443 { 444 buffer.append(", opResponseControls={"); 445 446 for (final int msgID : opResponseControls.keySet()) 447 { 448 buffer.append("opMsgID="); 449 buffer.append(msgID); 450 buffer.append(", opControls={"); 451 452 boolean first = true; 453 for (final Control c : opResponseControls.get(msgID)) 454 { 455 if (first) 456 { 457 first = false; 458 } 459 else 460 { 461 buffer.append(", "); 462 } 463 464 buffer.append(c); 465 } 466 buffer.append('}'); 467 } 468 469 buffer.append('}'); 470 } 471 472 final String diagnosticMessage = getDiagnosticMessage(); 473 if (diagnosticMessage != null) 474 { 475 buffer.append(", diagnosticMessage='"); 476 buffer.append(diagnosticMessage); 477 buffer.append('\''); 478 } 479 480 final String matchedDN = getMatchedDN(); 481 if (matchedDN != null) 482 { 483 buffer.append(", matchedDN='"); 484 buffer.append(matchedDN); 485 buffer.append('\''); 486 } 487 488 final String[] referralURLs = getReferralURLs(); 489 if (referralURLs.length > 0) 490 { 491 buffer.append(", referralURLs={"); 492 for (int i=0; i < referralURLs.length; i++) 493 { 494 if (i > 0) 495 { 496 buffer.append(", "); 497 } 498 499 buffer.append('\''); 500 buffer.append(referralURLs[i]); 501 buffer.append('\''); 502 } 503 buffer.append('}'); 504 } 505 506 final Control[] responseControls = getResponseControls(); 507 if (responseControls.length > 0) 508 { 509 buffer.append(", responseControls={"); 510 for (int i=0; i < responseControls.length; i++) 511 { 512 if (i > 0) 513 { 514 buffer.append(", "); 515 } 516 517 buffer.append(responseControls[i]); 518 } 519 buffer.append('}'); 520 } 521 522 buffer.append(')'); 523 } 524}