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