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}