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