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}