/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.types; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.util.Reject; import org.opends.server.api.ClientConnection; import org.opends.server.api.plugin.PluginResult.OperationResult; import org.opends.server.controls.ControlDecoder; import org.opends.server.core.DirectoryServer; import org.opends.server.protocols.ldap.LDAPControl; import org.opends.server.types.operation.PostResponseOperation; import org.opends.server.types.operation.PreParseOperation; /** * This class defines a generic operation that may be processed by the * Directory Server. Specific subclasses should implement specific * functionality appropriate for the type of operation. *

* Note that this class is not intended to be subclassed by any * third-party code outside of the OpenDJ project. It should only be * extended by the operation types included in the * {@code org.opends.server.core} package. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.VOLATILE, mayInstantiate=false, mayExtend=false, mayInvoke=true) public abstract class AbstractOperation implements Operation, PreParseOperation, PostResponseOperation { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The set of response controls that will always be returned for an abandon operation. */ protected static final List NO_RESPONSE_CONTROLS = new ArrayList<>(0); /** The client connection with which this operation is associated. */ protected final ClientConnection clientConnection; /** The message ID for this operation. */ protected final int messageID; /** The operation ID for this operation. */ protected final long operationID; /** Whether nanotime was used for this operation. */ private final boolean useNanoTime; /** The cancel request for this operation. */ protected CancelRequest cancelRequest; /** The cancel result for this operation. */ protected CancelResult cancelResult; /** * Indicates whether this is an internal operation triggered within the server * itself rather than requested by an external client. */ private boolean isInternalOperation; private Boolean isInnerOperation; /** Indicates whether this operation is involved in data synchronization processing. */ private boolean isSynchronizationOperation; /** The entry for the authorization identify for this operation. */ private Entry authorizationEntry; /** * A set of attachments associated with this operation that might be used by * various components during its processing. */ private Map attachments = new HashMap<>(); /** The set of controls included in the request from the client. */ private final List requestControls; /** The result code for this operation. */ private ResultCode resultCode = ResultCode.UNDEFINED; /** * The error message for this operation that should be included in the log and in the response to * the client. */ private LocalizableMessageBuilder errorMessage = new LocalizableMessageBuilder(); /** The matched DN for this operation. */ private DN matchedDN; /** The set of referral URLs for this operation. */ private List referralURLs; /** * The real, masked result code for this operation that will not be included * in the response to the client, but will be logged. */ private ResultCode maskedResultCode; /** * The real, masked error message for this operation that will not be included * in the response to the client, but will be logged. */ private LocalizableMessageBuilder maskedErrorMessage; /** Additional information that should be included in the log but not sent to the client. */ private List additionalLogItems; /** Indicates whether this operation needs to be synchronized to other copies of the data. */ private boolean dontSynchronizeFlag; /** The time that processing started on this operation in milliseconds. */ private long processingStartTime; /** The time that processing ended on this operation in milliseconds. */ private long processingStopTime; /** The time that processing started on this operation in nanoseconds. */ private long processingStartNanoTime; /** The time that processing ended on this operation in nanoseconds. */ private long processingStopNanoTime; /** The callbacks to be invoked once a response has been sent. */ private List postResponseCallbacks; /** * Creates a new operation with the provided information. * * @param clientConnection The client connection with which this * operation is associated. * @param operationID The identifier assigned to this * operation for the client connection. * @param messageID The message ID of the request with * which this operation is associated. * @param requestControls The set of controls included in the * request. */ protected AbstractOperation(ClientConnection clientConnection, long operationID, int messageID, List requestControls) { this.clientConnection = clientConnection; this.operationID = operationID; this.messageID = messageID; this.useNanoTime = DirectoryServer.getUseNanoTime(); if (requestControls == null) { this.requestControls = new ArrayList<>(0); } else { this.requestControls = requestControls; } authorizationEntry = clientConnection.getAuthenticationInfo().getAuthorizationEntry(); } @Override public void disconnectClient(DisconnectReason disconnectReason, boolean sendNotification, LocalizableMessage message) { clientConnection.disconnect(disconnectReason, sendNotification, message); } @Override public final ClientConnection getClientConnection() { return clientConnection; } @Override public final long getConnectionID() { return clientConnection.getConnectionID(); } @Override public final long getOperationID() { return operationID; } @Override public final int getMessageID() { return messageID; } @Override public final List getRequestControls() { return requestControls; } @Override @SuppressWarnings("unchecked") public final T getRequestControl( ControlDecoder d) throws DirectoryException { String oid = d.getOID(); for(int i = 0; i < requestControls.size(); i++) { Control c = requestControls.get(i); if(c.getOID().equals(oid)) { if(c instanceof LDAPControl) { T decodedControl = d.decode(c.isCritical(), ((LDAPControl) c).getValue()); requestControls.set(i, decodedControl); return decodedControl; } else { return (T)c; } } } return null; } @Override public final void addRequestControl(Control control) { requestControls.add(control); } @Override public final ResultCode getResultCode() { return resultCode; } @Override public final void setResultCode(ResultCode resultCode) { this.resultCode = resultCode; } @Override public final ResultCode getMaskedResultCode() { return maskedResultCode; } @Override public final void setMaskedResultCode(ResultCode maskedResultCode) { this.maskedResultCode = maskedResultCode; } @Override public final LocalizableMessageBuilder getErrorMessage() { return errorMessage; } @Override public final void setErrorMessage(LocalizableMessageBuilder errorMessage) { this.errorMessage = errorMessage; } @Override public final void appendErrorMessage(LocalizableMessage message) { if (errorMessage == null) { errorMessage = new LocalizableMessageBuilder(); } if (message != null) { if (errorMessage.length() > 0) { errorMessage.append(" "); } errorMessage.append(message); } } @Override public final LocalizableMessageBuilder getMaskedErrorMessage() { return maskedErrorMessage; } @Override public final void setMaskedErrorMessage(LocalizableMessageBuilder maskedErrorMessage) { this.maskedErrorMessage = maskedErrorMessage; } @Override public final void appendMaskedErrorMessage(LocalizableMessage maskedMessage) { if (maskedErrorMessage == null) { maskedErrorMessage = new LocalizableMessageBuilder(); } else if (maskedErrorMessage.length() > 0) { maskedErrorMessage.append(" "); } maskedErrorMessage.append(maskedMessage); } @Override public List getAdditionalLogItems() { if (additionalLogItems != null) { return Collections.unmodifiableList(additionalLogItems); } return Collections.emptyList(); } @Override public void addAdditionalLogItem(AdditionalLogItem item) { Reject.ifNull(item); if (additionalLogItems == null) { additionalLogItems = new LinkedList<>(); } additionalLogItems.add(item); } @Override public final DN getMatchedDN() { return matchedDN; } @Override public final void setMatchedDN(DN matchedDN) { this.matchedDN = matchedDN; } @Override public final List getReferralURLs() { return referralURLs; } @Override public final void setReferralURLs(List referralURLs) { this.referralURLs = referralURLs; } @Override public final void setResponseData( DirectoryException directoryException) { this.resultCode = directoryException.getResultCode(); this.maskedResultCode = directoryException.getMaskedResultCode(); this.matchedDN = directoryException.getMatchedDN(); this.referralURLs = directoryException.getReferralURLs(); appendErrorMessage(directoryException.getMessageObject()); final LocalizableMessage maskedMessage = directoryException.getMaskedMessage(); if (maskedMessage != null) { appendMaskedErrorMessage(maskedMessage); } } @Override public final boolean isInternalOperation() { return isInternalOperation; } @Override public final void setInternalOperation(boolean isInternalOperation) { this.isInternalOperation = isInternalOperation; } @Override public boolean isInnerOperation() { if (this.isInnerOperation != null) { return this.isInnerOperation; } return isInternalOperation(); } @Override public void setInnerOperation(boolean isInnerOperation) { this.isInnerOperation = isInnerOperation; } @Override public final boolean isSynchronizationOperation() { return isSynchronizationOperation; } @Override public final void setSynchronizationOperation( boolean isSynchronizationOperation) { this.isSynchronizationOperation = isSynchronizationOperation; } @Override public boolean dontSynchronize() { return dontSynchronizeFlag; } @Override public final void setDontSynchronize(boolean dontSynchronize) { this.dontSynchronizeFlag = dontSynchronize; } @Override public final Entry getAuthorizationEntry() { return authorizationEntry; } @Override public final void setAuthorizationEntry(Entry authorizationEntry) { this.authorizationEntry = authorizationEntry; } @Override public final DN getAuthorizationDN() { if (authorizationEntry != null) { return authorizationEntry.getName(); } return DN.rootDN(); } @Override public final Map getAttachments() { return attachments; } @Override public final void setAttachments(Map attachments) { this.attachments = attachments; } @Override @SuppressWarnings("unchecked") public final T getAttachment(String name) { return (T) attachments.get(name); } @Override @SuppressWarnings("unchecked") public final T removeAttachment(String name) { return (T) attachments.remove(name); } @Override @SuppressWarnings("unchecked") public final T setAttachment(String name, Object value) { return (T) attachments.put(name, value); } @Override public final void operationCompleted() { // Notify the client connection that this operation is complete // and that it no longer needs to be retained. clientConnection.removeOperationInProgress(messageID); } @Override public CancelResult cancel(CancelRequest cancelRequest) { abort(cancelRequest); long stopWaitingTime = System.currentTimeMillis() + 5000; while (cancelResult == null && System.currentTimeMillis() < stopWaitingTime) { try { Thread.sleep(50); } catch (Exception e) { logger.traceException(e); } } if (cancelResult == null) { // This can happen in some rare cases (e.g., if a client // disconnects and there is still a lot of data to send to // that client), and in this case we'll prevent the cancel // thread from blocking for a long period of time. cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL, null); } return cancelResult; } @Override public synchronized void abort(CancelRequest cancelRequest) { if(cancelResult == null && this.cancelRequest == null) { this.cancelRequest = cancelRequest; } } @Override public final synchronized void checkIfCanceled(boolean signalTooLate) throws CanceledOperationException { if(cancelRequest != null) { throw new CanceledOperationException(cancelRequest); } if(signalTooLate && cancelResult != null) { cancelResult = new CancelResult(ResultCode.TOO_LATE, null); } } @Override public final CancelRequest getCancelRequest() { return cancelRequest; } @Override public final CancelResult getCancelResult() { return cancelResult; } @Override public final String toString() { StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } @Override public final long getProcessingStartTime() { return processingStartTime; } /** * Set the time at which the processing started for this operation. */ public final void setProcessingStartTime() { processingStartTime = System.currentTimeMillis(); if(useNanoTime) { processingStartNanoTime = System.nanoTime(); } } @Override public final long getProcessingStopTime() { return processingStopTime; } /** * Set the time at which the processing stopped for this operation. * This will actually hold a time immediately before the response * was sent to the client. */ public final void setProcessingStopTime() { this.processingStopTime = System.currentTimeMillis(); if(useNanoTime) { this.processingStopNanoTime = System.nanoTime(); } } @Override public final long getProcessingTime() { return processingStopTime - processingStartTime; } @Override public final long getProcessingNanoTime() { if(useNanoTime) { return processingStopNanoTime - processingStartNanoTime; } return -1; } @Override public final void registerPostResponseCallback(Runnable callback) { if (postResponseCallbacks == null) { postResponseCallbacks = new LinkedList<>(); } postResponseCallbacks.add(callback); } @Override public final int hashCode() { return clientConnection.hashCode() * (int) operationID; } @Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Operation) { Operation other = (Operation) obj; if (other.getClientConnection().equals(clientConnection)) { return other.getOperationID() == operationID; } } return false; } /** * Invokes the post response callbacks that were registered with * this operation. */ protected final void invokePostResponseCallbacks() { if (postResponseCallbacks != null) { for (Runnable callback : postResponseCallbacks) { try { callback.run(); } catch (Exception e) { // Should not happen. logger.traceException(e); } } } } /** * Updates the error message and the result code of the operation. This method * is called because no workflows were found to process the operation. */ public void updateOperationErrMsgAndResCode() { // do nothing by default } /** * Processes the provided operation result for the current operation. * * @param operationResult the operation result * @return {@code true} if processing can continue, {@code false} otherwise */ public boolean processOperationResult(OperationResult operationResult) { return processOperationResult(this, operationResult); } /** * Processes the provided operation result for the provided operation. * * @param op the operation * @param opResult the operation result * @return {@code true} if processing can continue, {@code false} otherwise */ public static boolean processOperationResult(Operation op, OperationResult opResult) { if (!opResult.continueProcessing()) { op.setResultCode(opResult.getResultCode()); op.appendErrorMessage(opResult.getErrorMessage()); op.setMatchedDN(opResult.getMatchedDN()); op.setReferralURLs(opResult.getReferralURLs()); return false; } return true; } }