001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.types; 028 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Map; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.LocalizableMessageBuilder; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.ldap.ResultCode; 040import org.forgerock.util.Reject; 041import org.opends.server.api.ClientConnection; 042import org.opends.server.api.plugin.PluginResult.OperationResult; 043import org.opends.server.controls.ControlDecoder; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.protocols.ldap.LDAPControl; 046import org.opends.server.types.operation.PostResponseOperation; 047import org.opends.server.types.operation.PreParseOperation; 048 049/** 050 * This class defines a generic operation that may be processed by the 051 * Directory Server. Specific subclasses should implement specific 052 * functionality appropriate for the type of operation. 053 * <BR><BR> 054 * Note that this class is not intended to be subclassed by any 055 * third-party code outside of the OpenDJ project. It should only be 056 * extended by the operation types included in the 057 * {@code org.opends.server.core} package. 058 */ 059@org.opends.server.types.PublicAPI( 060 stability=org.opends.server.types.StabilityLevel.VOLATILE, 061 mayInstantiate=false, 062 mayExtend=false, 063 mayInvoke=true) 064public abstract class AbstractOperation 065 implements Operation, PreParseOperation, PostResponseOperation 066{ 067 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 068 069 /** The set of response controls that will always be returned for an abandon operation. */ 070 protected static final List<Control> NO_RESPONSE_CONTROLS = new ArrayList<>(0); 071 072 /** The client connection with which this operation is associated. */ 073 protected final ClientConnection clientConnection; 074 /** The message ID for this operation. */ 075 protected final int messageID; 076 /** The operation ID for this operation. */ 077 protected final long operationID; 078 079 /** Whether nanotime was used for this operation. */ 080 private final boolean useNanoTime; 081 082 /** The cancel request for this operation. */ 083 protected CancelRequest cancelRequest; 084 /** The cancel result for this operation. */ 085 protected CancelResult cancelResult; 086 087 /** 088 * Indicates whether this is an internal operation triggered within the server 089 * itself rather than requested by an external client. 090 */ 091 private boolean isInternalOperation; 092 private Boolean isInnerOperation; 093 094 /** Indicates whether this operation is involved in data synchronization processing. */ 095 private boolean isSynchronizationOperation; 096 097 /** The entry for the authorization identify for this operation. */ 098 private Entry authorizationEntry; 099 100 /** 101 * A set of attachments associated with this operation that might be used by 102 * various components during its processing. 103 */ 104 private Map<String, Object> attachments = new HashMap<>(); 105 106 /** The set of controls included in the request from the client. */ 107 private final List<Control> requestControls; 108 109 /** The result code for this operation. */ 110 private ResultCode resultCode = ResultCode.UNDEFINED; 111 /** 112 * The error message for this operation that should be included in the log and in the response to 113 * the client. 114 */ 115 private LocalizableMessageBuilder errorMessage = new LocalizableMessageBuilder(); 116 /** The matched DN for this operation. */ 117 private DN matchedDN; 118 /** The set of referral URLs for this operation. */ 119 private List<String> referralURLs; 120 121 /** 122 * The real, masked result code for this operation that will not be included 123 * in the response to the client, but will be logged. 124 */ 125 private ResultCode maskedResultCode; 126 /** 127 * The real, masked error message for this operation that will not be included 128 * in the response to the client, but will be logged. 129 */ 130 private LocalizableMessageBuilder maskedErrorMessage; 131 132 /** Additional information that should be included in the log but not sent to the client. */ 133 private List<AdditionalLogItem> additionalLogItems; 134 135 /** Indicates whether this operation needs to be synchronized to other copies of the data. */ 136 private boolean dontSynchronizeFlag; 137 138 /** The time that processing started on this operation in milliseconds. */ 139 private long processingStartTime; 140 /** The time that processing ended on this operation in milliseconds. */ 141 private long processingStopTime; 142 /** The time that processing started on this operation in nanoseconds. */ 143 private long processingStartNanoTime; 144 /** The time that processing ended on this operation in nanoseconds. */ 145 private long processingStopNanoTime; 146 147 /** The callbacks to be invoked once a response has been sent. */ 148 private List<Runnable> postResponseCallbacks; 149 150 /** 151 * Creates a new operation with the provided information. 152 * 153 * @param clientConnection The client connection with which this 154 * operation is associated. 155 * @param operationID The identifier assigned to this 156 * operation for the client connection. 157 * @param messageID The message ID of the request with 158 * which this operation is associated. 159 * @param requestControls The set of controls included in the 160 * request. 161 */ 162 protected AbstractOperation(ClientConnection clientConnection, 163 long operationID, 164 int messageID, List<Control> requestControls) 165 { 166 this.clientConnection = clientConnection; 167 this.operationID = operationID; 168 this.messageID = messageID; 169 this.useNanoTime = DirectoryServer.getUseNanoTime(); 170 171 if (requestControls == null) 172 { 173 this.requestControls = new ArrayList<>(0); 174 } 175 else 176 { 177 this.requestControls = requestControls; 178 } 179 180 authorizationEntry = clientConnection.getAuthenticationInfo().getAuthorizationEntry(); 181 } 182 183 184 @Override 185 public void disconnectClient(DisconnectReason disconnectReason, 186 boolean sendNotification, 187 LocalizableMessage message) 188 { 189 clientConnection.disconnect(disconnectReason, sendNotification, message); 190 } 191 192 @Override 193 public final ClientConnection getClientConnection() 194 { 195 return clientConnection; 196 } 197 198 @Override 199 public final long getConnectionID() 200 { 201 return clientConnection.getConnectionID(); 202 } 203 204 @Override 205 public final long getOperationID() 206 { 207 return operationID; 208 } 209 210 @Override 211 public final int getMessageID() 212 { 213 return messageID; 214 } 215 216 @Override 217 public final List<Control> getRequestControls() 218 { 219 return requestControls; 220 } 221 222 @Override 223 @SuppressWarnings("unchecked") 224 public final <T extends Control> T getRequestControl( 225 ControlDecoder<T> d) throws DirectoryException 226 { 227 String oid = d.getOID(); 228 for(int i = 0; i < requestControls.size(); i++) 229 { 230 Control c = requestControls.get(i); 231 if(c.getOID().equals(oid)) 232 { 233 if(c instanceof LDAPControl) 234 { 235 T decodedControl = d.decode(c.isCritical(), 236 ((LDAPControl) c).getValue()); 237 requestControls.set(i, decodedControl); 238 return decodedControl; 239 } 240 else 241 { 242 return (T)c; 243 } 244 } 245 } 246 return null; 247 } 248 249 @Override 250 public final void addRequestControl(Control control) 251 { 252 requestControls.add(control); 253 } 254 255 @Override 256 public final ResultCode getResultCode() 257 { 258 return resultCode; 259 } 260 261 @Override 262 public final void setResultCode(ResultCode resultCode) 263 { 264 this.resultCode = resultCode; 265 } 266 267 @Override 268 public final ResultCode getMaskedResultCode() 269 { 270 return maskedResultCode; 271 } 272 273 @Override 274 public final void setMaskedResultCode(ResultCode maskedResultCode) 275 { 276 this.maskedResultCode = maskedResultCode; 277 } 278 279 @Override 280 public final LocalizableMessageBuilder getErrorMessage() 281 { 282 return errorMessage; 283 } 284 285 @Override 286 public final void setErrorMessage(LocalizableMessageBuilder errorMessage) 287 { 288 this.errorMessage = errorMessage; 289 } 290 291 @Override 292 public final void appendErrorMessage(LocalizableMessage message) 293 { 294 if (errorMessage == null) 295 { 296 errorMessage = new LocalizableMessageBuilder(); 297 } 298 if (message != null) 299 { 300 if (errorMessage.length() > 0) 301 { 302 errorMessage.append(" "); 303 } 304 errorMessage.append(message); 305 } 306 } 307 308 @Override 309 public final LocalizableMessageBuilder getMaskedErrorMessage() 310 { 311 return maskedErrorMessage; 312 } 313 314 @Override 315 public final void setMaskedErrorMessage(LocalizableMessageBuilder maskedErrorMessage) 316 { 317 this.maskedErrorMessage = maskedErrorMessage; 318 } 319 320 @Override 321 public final void appendMaskedErrorMessage(LocalizableMessage maskedMessage) 322 { 323 if (maskedErrorMessage == null) 324 { 325 maskedErrorMessage = new LocalizableMessageBuilder(); 326 } 327 else if (maskedErrorMessage.length() > 0) 328 { 329 maskedErrorMessage.append(" "); 330 } 331 332 maskedErrorMessage.append(maskedMessage); 333 } 334 335 @Override 336 public List<AdditionalLogItem> getAdditionalLogItems() 337 { 338 if (additionalLogItems != null) 339 { 340 return Collections.unmodifiableList(additionalLogItems); 341 } 342 return Collections.emptyList(); 343 } 344 345 @Override 346 public void addAdditionalLogItem(AdditionalLogItem item) 347 { 348 Reject.ifNull(item); 349 if (additionalLogItems == null) 350 { 351 additionalLogItems = new LinkedList<>(); 352 } 353 additionalLogItems.add(item); 354 } 355 356 @Override 357 public final DN getMatchedDN() 358 { 359 return matchedDN; 360 } 361 362 @Override 363 public final void setMatchedDN(DN matchedDN) 364 { 365 this.matchedDN = matchedDN; 366 } 367 368 @Override 369 public final List<String> getReferralURLs() 370 { 371 return referralURLs; 372 } 373 374 @Override 375 public final void setReferralURLs(List<String> referralURLs) 376 { 377 this.referralURLs = referralURLs; 378 } 379 380 @Override 381 public final void setResponseData( 382 DirectoryException directoryException) 383 { 384 this.resultCode = directoryException.getResultCode(); 385 this.maskedResultCode = directoryException.getMaskedResultCode(); 386 this.matchedDN = directoryException.getMatchedDN(); 387 this.referralURLs = directoryException.getReferralURLs(); 388 389 appendErrorMessage(directoryException.getMessageObject()); 390 final LocalizableMessage maskedMessage = directoryException.getMaskedMessage(); 391 if (maskedMessage != null) { 392 appendMaskedErrorMessage(maskedMessage); 393 } 394 } 395 396 @Override 397 public final boolean isInternalOperation() 398 { 399 return isInternalOperation; 400 } 401 402 @Override 403 public final void setInternalOperation(boolean isInternalOperation) 404 { 405 this.isInternalOperation = isInternalOperation; 406 } 407 408 @Override 409 public boolean isInnerOperation() 410 { 411 if (this.isInnerOperation != null) 412 { 413 return this.isInnerOperation; 414 } 415 return isInternalOperation(); 416 } 417 418 @Override 419 public void setInnerOperation(boolean isInnerOperation) 420 { 421 this.isInnerOperation = isInnerOperation; 422 } 423 424 425 @Override 426 public final boolean isSynchronizationOperation() 427 { 428 return isSynchronizationOperation; 429 } 430 431 @Override 432 public final void setSynchronizationOperation( 433 boolean isSynchronizationOperation) 434 { 435 this.isSynchronizationOperation = isSynchronizationOperation; 436 } 437 438 @Override 439 public boolean dontSynchronize() 440 { 441 return dontSynchronizeFlag; 442 } 443 444 @Override 445 public final void setDontSynchronize(boolean dontSynchronize) 446 { 447 this.dontSynchronizeFlag = dontSynchronize; 448 } 449 450 @Override 451 public final Entry getAuthorizationEntry() 452 { 453 return authorizationEntry; 454 } 455 456 @Override 457 public final void setAuthorizationEntry(Entry authorizationEntry) 458 { 459 this.authorizationEntry = authorizationEntry; 460 } 461 462 @Override 463 public final DN getAuthorizationDN() 464 { 465 if (authorizationEntry != null) 466 { 467 return authorizationEntry.getName(); 468 } 469 return DN.rootDN(); 470 } 471 472 @Override 473 public final Map<String,Object> getAttachments() 474 { 475 return attachments; 476 } 477 478 @Override 479 public final void setAttachments(Map<String, Object> attachments) 480 { 481 this.attachments = attachments; 482 } 483 484 @Override 485 @SuppressWarnings("unchecked") 486 public final <T> T getAttachment(String name) 487 { 488 return (T) attachments.get(name); 489 } 490 491 @Override 492 @SuppressWarnings("unchecked") 493 public final <T> T removeAttachment(String name) 494 { 495 return (T) attachments.remove(name); 496 } 497 498 @Override 499 @SuppressWarnings("unchecked") 500 public final <T> T setAttachment(String name, Object value) 501 { 502 return (T) attachments.put(name, value); 503 } 504 505 @Override 506 public final void operationCompleted() 507 { 508 // Notify the client connection that this operation is complete 509 // and that it no longer needs to be retained. 510 clientConnection.removeOperationInProgress(messageID); 511 } 512 513 @Override 514 public CancelResult cancel(CancelRequest cancelRequest) 515 { 516 abort(cancelRequest); 517 518 long stopWaitingTime = System.currentTimeMillis() + 5000; 519 while (cancelResult == null && System.currentTimeMillis() < stopWaitingTime) 520 { 521 try 522 { 523 Thread.sleep(50); 524 } 525 catch (Exception e) 526 { 527 logger.traceException(e); 528 } 529 } 530 531 if (cancelResult == null) 532 { 533 // This can happen in some rare cases (e.g., if a client 534 // disconnects and there is still a lot of data to send to 535 // that client), and in this case we'll prevent the cancel 536 // thread from blocking for a long period of time. 537 cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL, null); 538 } 539 540 return cancelResult; 541 } 542 543 @Override 544 public synchronized void abort(CancelRequest cancelRequest) 545 { 546 if(cancelResult == null && this.cancelRequest == null) 547 { 548 this.cancelRequest = cancelRequest; 549 } 550 } 551 552 @Override 553 public final synchronized void checkIfCanceled(boolean signalTooLate) 554 throws CanceledOperationException 555 { 556 if(cancelRequest != null) 557 { 558 throw new CanceledOperationException(cancelRequest); 559 } 560 561 if(signalTooLate && cancelResult != null) 562 { 563 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 564 } 565 } 566 567 @Override 568 public final CancelRequest getCancelRequest() 569 { 570 return cancelRequest; 571 } 572 573 @Override 574 public final CancelResult getCancelResult() 575 { 576 return cancelResult; 577 } 578 579 @Override 580 public final String toString() 581 { 582 StringBuilder buffer = new StringBuilder(); 583 toString(buffer); 584 return buffer.toString(); 585 } 586 587 @Override 588 public final long getProcessingStartTime() 589 { 590 return processingStartTime; 591 } 592 593 /** 594 * Set the time at which the processing started for this operation. 595 */ 596 public final void setProcessingStartTime() 597 { 598 processingStartTime = System.currentTimeMillis(); 599 if(useNanoTime) 600 { 601 processingStartNanoTime = System.nanoTime(); 602 } 603 } 604 605 @Override 606 public final long getProcessingStopTime() 607 { 608 return processingStopTime; 609 } 610 611 /** 612 * Set the time at which the processing stopped for this operation. 613 * This will actually hold a time immediately before the response 614 * was sent to the client. 615 */ 616 public final void setProcessingStopTime() 617 { 618 this.processingStopTime = System.currentTimeMillis(); 619 if(useNanoTime) 620 { 621 this.processingStopNanoTime = System.nanoTime(); 622 } 623 } 624 625 @Override 626 public final long getProcessingTime() 627 { 628 return processingStopTime - processingStartTime; 629 } 630 631 @Override 632 public final long getProcessingNanoTime() 633 { 634 if(useNanoTime) 635 { 636 return processingStopNanoTime - processingStartNanoTime; 637 } 638 return -1; 639 } 640 641 @Override 642 public final void registerPostResponseCallback(Runnable callback) 643 { 644 if (postResponseCallbacks == null) 645 { 646 postResponseCallbacks = new LinkedList<>(); 647 } 648 postResponseCallbacks.add(callback); 649 } 650 651 @Override 652 public final int hashCode() 653 { 654 return clientConnection.hashCode() * (int) operationID; 655 } 656 657 @Override 658 public final boolean equals(Object obj) 659 { 660 if (this == obj) 661 { 662 return true; 663 } 664 if (obj instanceof Operation) 665 { 666 Operation other = (Operation) obj; 667 if (other.getClientConnection().equals(clientConnection)) 668 { 669 return other.getOperationID() == operationID; 670 } 671 } 672 return false; 673 } 674 675 676 677 /** 678 * Invokes the post response callbacks that were registered with 679 * this operation. 680 */ 681 protected final void invokePostResponseCallbacks() 682 { 683 if (postResponseCallbacks != null) 684 { 685 for (Runnable callback : postResponseCallbacks) 686 { 687 try 688 { 689 callback.run(); 690 } 691 catch (Exception e) 692 { 693 // Should not happen. 694 logger.traceException(e); 695 } 696 } 697 } 698 } 699 700 /** 701 * Updates the error message and the result code of the operation. This method 702 * is called because no workflows were found to process the operation. 703 */ 704 public void updateOperationErrMsgAndResCode() 705 { 706 // do nothing by default 707 } 708 709 /** 710 * Processes the provided operation result for the current operation. 711 * 712 * @param operationResult the operation result 713 * @return {@code true} if processing can continue, {@code false} otherwise 714 */ 715 public boolean processOperationResult(OperationResult operationResult) 716 { 717 return processOperationResult(this, operationResult); 718 } 719 720 /** 721 * Processes the provided operation result for the provided operation. 722 * 723 * @param op the operation 724 * @param opResult the operation result 725 * @return {@code true} if processing can continue, {@code false} otherwise 726 */ 727 public static boolean processOperationResult(Operation op, OperationResult opResult) 728 { 729 if (!opResult.continueProcessing()) 730 { 731 op.setResultCode(opResult.getResultCode()); 732 op.appendErrorMessage(opResult.getErrorMessage()); 733 op.setMatchedDN(opResult.getMatchedDN()); 734 op.setReferralURLs(opResult.getReferralURLs()); 735 return false; 736 } 737 return true; 738 } 739}