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 2007-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS
026 */
027package org.opends.server.core;
028
029import java.util.ArrayList;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Set;
033
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.forgerock.opendj.ldap.ByteString;
036import org.forgerock.opendj.ldap.ResultCode;
037import org.opends.server.api.ClientConnection;
038import org.opends.server.types.*;
039import org.opends.server.types.operation.PostResponseCompareOperation;
040import org.opends.server.types.operation.PreParseCompareOperation;
041import org.opends.server.workflowelement.localbackend.LocalBackendCompareOperation;
042
043import static org.opends.messages.CoreMessages.*;
044import static org.opends.server.core.DirectoryServer.*;
045import static org.opends.server.loggers.AccessLogger.*;
046import static org.opends.server.util.StaticUtils.*;
047import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*;
048
049/**
050 * This class defines an operation that may be used to determine whether a
051 * specified entry in the Directory Server contains a given attribute-value
052 * pair.
053 */
054public class CompareOperationBasis
055             extends AbstractOperation
056             implements PreParseCompareOperation, CompareOperation,
057                        PostResponseCompareOperation
058{
059  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
060
061  /** The attribute type for this compare operation. */
062  private AttributeType attributeType;
063
064  /** The assertion value for the compare operation. */
065  private ByteString assertionValue;
066
067  /** The set of attribute options. */
068  private Set<String> attributeOptions;
069
070  /** The raw, unprocessed entry DN as included in the client request. */
071  private ByteString rawEntryDN;
072
073  /** The DN of the entry for the compare operation. */
074  private DN entryDN;
075
076  /** The proxied authorization target DN for this operation. */
077  private DN proxiedAuthorizationDN;
078
079  /** The set of response controls for this compare operation. */
080  private List<Control> responseControls;
081
082  /** The attribute type for the compare operation. */
083  private String rawAttributeType;
084
085
086
087  /**
088   * Creates a new compare operation with the provided information.
089   *
090   * @param  clientConnection  The client connection with which this operation
091   *                           is associated.
092   * @param  operationID       The operation ID for this operation.
093   * @param  messageID         The message ID of the request with which this
094   *                           operation is associated.
095   * @param  requestControls   The set of controls included in the request.
096   * @param  rawEntryDN        The raw, unprocessed entry DN as provided in the
097   *                           client request.  This may or may not be a valid
098   *                           DN as no validation will have been performed yet.
099   * @param  rawAttributeType  The raw attribute type for the compare operation.
100   * @param  assertionValue    The assertion value for the compare operation.
101   */
102  public CompareOperationBasis(
103                          ClientConnection clientConnection, long operationID,
104                          int messageID, List<Control> requestControls,
105                          ByteString rawEntryDN, String rawAttributeType,
106                          ByteString assertionValue)
107  {
108    super(clientConnection, operationID, messageID, requestControls);
109
110
111    this.rawEntryDN       = rawEntryDN;
112    this.rawAttributeType = rawAttributeType;
113    this.assertionValue   = assertionValue;
114
115    responseControls       = new ArrayList<>();
116    entryDN                = null;
117    attributeType          = null;
118    attributeOptions       = null;
119    cancelRequest          = null;
120    proxiedAuthorizationDN = null;
121  }
122
123
124
125  /**
126   * Creates a new compare operation with the provided information.
127   *
128   * @param  clientConnection  The client connection with which this operation
129   *                           is associated.
130   * @param  operationID       The operation ID for this operation.
131   * @param  messageID         The message ID of the request with which this
132   *                           operation is associated.
133   * @param  requestControls   The set of controls included in the request.
134   * @param  entryDN           The entry DN for this compare operation.
135   * @param  attributeType     The attribute type for this compare operation.
136   * @param  assertionValue    The assertion value for the compare operation.
137   */
138  public CompareOperationBasis(
139                          ClientConnection clientConnection, long operationID,
140                          int messageID, List<Control> requestControls,
141                          DN entryDN, AttributeType attributeType,
142                          ByteString assertionValue)
143  {
144    super(clientConnection, operationID, messageID, requestControls);
145
146
147    this.entryDN        = entryDN;
148    this.attributeType  = attributeType;
149    this.assertionValue = assertionValue;
150
151    responseControls       = new ArrayList<>();
152    rawEntryDN             = ByteString.valueOfUtf8(entryDN.toString());
153    rawAttributeType       = attributeType.getNameOrOID();
154    cancelRequest          = null;
155    proxiedAuthorizationDN = null;
156    attributeOptions       = new HashSet<>();
157  }
158
159  /** {@inheritDoc} */
160  @Override
161  public final ByteString getRawEntryDN()
162  {
163    return rawEntryDN;
164  }
165
166  /** {@inheritDoc} */
167  @Override
168  public final void setRawEntryDN(ByteString rawEntryDN)
169  {
170    this.rawEntryDN = rawEntryDN;
171
172    entryDN = null;
173  }
174
175  /** {@inheritDoc} */
176  @Override
177  public final DN getEntryDN()
178  {
179    if (entryDN == null) {
180      try
181      {
182        entryDN = DN.decode(rawEntryDN);
183      }
184      catch (DirectoryException de)
185      {
186        logger.traceException(de);
187
188        setResultCode(de.getResultCode());
189        appendErrorMessage(de.getMessageObject());
190      }
191    }
192    return entryDN;
193  }
194
195  /** {@inheritDoc} */
196  @Override
197  public final String getRawAttributeType()
198  {
199    return rawAttributeType;
200  }
201
202  /** {@inheritDoc} */
203  @Override
204  public final void setRawAttributeType(String rawAttributeType)
205  {
206    this.rawAttributeType = rawAttributeType;
207
208    attributeType = null;
209    attributeOptions = null;
210  }
211
212  private void getAttributeTypeAndOptions() {
213    String baseName;
214    int semicolonPos = rawAttributeType.indexOf(';');
215    if (semicolonPos > 0) {
216      baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
217
218      attributeOptions = new HashSet<>();
219      int nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
220      while (nextPos > 0)
221      {
222        attributeOptions.add(
223            rawAttributeType.substring(semicolonPos+1, nextPos));
224        semicolonPos = nextPos;
225        nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
226      }
227
228      attributeOptions.add(rawAttributeType.substring(semicolonPos+1));
229    }
230    else
231    {
232      baseName = toLowerCase(rawAttributeType);
233      attributeOptions  = null;
234    }
235    attributeType = DirectoryServer.getAttributeTypeOrDefault(baseName);
236  }
237
238  /** {@inheritDoc} */
239  @Override
240  public final AttributeType getAttributeType()
241  {
242    if (attributeType == null) {
243      getAttributeTypeAndOptions();
244    }
245    return attributeType;
246  }
247
248  /** {@inheritDoc} */
249  @Override
250  public void setAttributeType(AttributeType attributeType)
251  {
252    this.attributeType = attributeType;
253  }
254
255  /** {@inheritDoc} */
256  @Override
257  public Set<String> getAttributeOptions()
258  {
259    if (attributeOptions == null) {
260      getAttributeTypeAndOptions();
261    }
262    return attributeOptions;
263  }
264
265  /** {@inheritDoc} */
266  @Override
267  public void setAttributeOptions(Set<String> attributeOptions)
268  {
269    this.attributeOptions = attributeOptions;
270  }
271
272  /** {@inheritDoc} */
273  @Override
274  public final ByteString getAssertionValue()
275  {
276    return assertionValue;
277  }
278
279  /** {@inheritDoc} */
280  @Override
281  public final void setAssertionValue(ByteString assertionValue)
282  {
283    this.assertionValue = assertionValue;
284  }
285
286  /** {@inheritDoc} */
287  @Override
288  public final OperationType getOperationType()
289  {
290    // Note that no debugging will be done in this method because it is a likely
291    // candidate for being called by the logging subsystem.
292    return OperationType.COMPARE;
293  }
294
295
296
297  /**
298   * Retrieves the proxied authorization DN for this operation if proxied
299   * authorization has been requested.
300   *
301   * @return  The proxied authorization DN for this operation if proxied
302   *          authorization has been requested, or {@code null} if proxied
303   *          authorization has not been requested.
304   */
305  @Override
306  public DN getProxiedAuthorizationDN()
307  {
308    return proxiedAuthorizationDN;
309  }
310
311  /** {@inheritDoc} */
312  @Override
313  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
314  {
315    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
316  }
317
318  /** {@inheritDoc} */
319  @Override
320  public final List<Control> getResponseControls()
321  {
322    return responseControls;
323  }
324
325  /** {@inheritDoc} */
326  @Override
327  public final void addResponseControl(Control control)
328  {
329    responseControls.add(control);
330  }
331
332  /** {@inheritDoc} */
333  @Override
334  public final void removeResponseControl(Control control)
335  {
336    responseControls.remove(control);
337  }
338
339
340
341  /**
342   * Performs the work of actually processing this operation.  This
343   * should include all processing for the operation, including
344   * invoking plugins, logging messages, performing access control,
345   * managing synchronization, and any other work that might need to
346   * be done in the course of processing.
347   */
348  @Override
349  public final void run()
350  {
351    setResultCode(ResultCode.UNDEFINED);
352
353    // Start the processing timer.
354    setProcessingStartTime();
355
356    logCompareRequest(this);
357
358    // This flag is set to true as soon as a workflow has been executed.
359    boolean workflowExecuted = false;
360    try
361    {
362      // Check for and handle a request to cancel this operation.
363      checkIfCanceled(false);
364
365      // Invoke the pre-parse compare plugins.
366      if (!processOperationResult(getPluginConfigManager().invokePreParseComparePlugins(this)))
367      {
368        return;
369      }
370
371
372      // Check for a request to cancel this operation.
373      checkIfCanceled(false);
374
375
376      // Process the entry DN to convert it from the raw form to the form
377      // required for the rest of the compare processing.
378      try
379      {
380        if (entryDN == null)
381        {
382          entryDN = DN.decode(rawEntryDN);
383        }
384      }
385      catch (DirectoryException de)
386      {
387        logger.traceException(de);
388
389        setResultCode(de.getResultCode());
390        appendErrorMessage(de.getMessageObject());
391
392        return;
393      }
394
395      workflowExecuted = execute(this, entryDN);
396    }
397    catch(CanceledOperationException coe)
398    {
399      logger.traceException(coe);
400
401      setResultCode(ResultCode.CANCELLED);
402      cancelResult = new CancelResult(ResultCode.CANCELLED, null);
403
404      appendErrorMessage(coe.getCancelRequest().getCancelReason());
405    }
406    finally
407    {
408      // Stop the processing timer.
409      setProcessingStopTime();
410
411      // Log the compare response message.
412      logCompareResponse(this);
413
414      if(cancelRequest == null || cancelResult == null ||
415          cancelResult.getResultCode() != ResultCode.CANCELLED ||
416          cancelRequest.notifyOriginalRequestor() ||
417          DirectoryServer.notifyAbandonedOperations())
418      {
419        clientConnection.sendResponse(this);
420      }
421
422      // Invoke the post-response compare plugins.
423      invokePostResponsePlugins(workflowExecuted);
424
425      // If no cancel result, set it
426      if(cancelResult == null)
427      {
428        cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
429      }
430    }
431  }
432
433
434  /**
435   * Invokes the post response plugins. If a workflow has been executed
436   * then invoke the post response plugins provided by the workflow
437   * elements of the workflow, otherwise invoke the post response plugins
438   * that have been registered with the current operation.
439   *
440   * @param workflowExecuted <code>true</code> if a workflow has been executed
441   */
442  private void invokePostResponsePlugins(boolean workflowExecuted)
443  {
444    // Invoke the post response plugins
445    if (workflowExecuted)
446    {
447      // Invoke the post response plugins that have been registered by
448      // the workflow elements
449      List<LocalBackendCompareOperation> localOperations =
450        (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
451
452      if (localOperations != null)
453      {
454        for (LocalBackendCompareOperation localOperation : localOperations)
455        {
456          getPluginConfigManager().invokePostResponseComparePlugins(localOperation);
457        }
458      }
459    }
460    else
461    {
462      // Invoke the post response plugins that have been registered with
463      // the current operation
464      getPluginConfigManager().invokePostResponseComparePlugins(this);
465    }
466  }
467
468
469  /**
470   * Updates the error message and the result code of the operation.
471   *
472   * This method is called because no workflow was found to process
473   * the operation.
474   */
475  @Override
476  public void updateOperationErrMsgAndResCode()
477  {
478    setResultCode(ResultCode.NO_SUCH_OBJECT);
479    appendErrorMessage(ERR_COMPARE_NO_SUCH_ENTRY.get(getEntryDN()));
480  }
481
482  /** {@inheritDoc} */
483  @Override
484  public final void toString(StringBuilder buffer)
485  {
486    buffer.append("CompareOperation(connID=");
487    buffer.append(clientConnection.getConnectionID());
488    buffer.append(", opID=");
489    buffer.append(operationID);
490    buffer.append(", dn=");
491    buffer.append(rawEntryDN);
492    buffer.append(", attr=");
493    buffer.append(rawAttributeType);
494    buffer.append(")");
495  }
496
497
498  /**
499   * {@inheritDoc}
500   *
501   * This method always returns null.
502   */
503  @Override
504  public Entry getEntryToCompare()
505  {
506    return null;
507  }
508
509}