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 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.core;
028
029import java.util.ArrayList;
030import java.util.List;
031
032import org.forgerock.i18n.slf4j.LocalizedLogger;
033import org.forgerock.opendj.ldap.ByteString;
034import org.forgerock.opendj.ldap.ResultCode;
035import org.opends.server.api.ClientConnection;
036import org.opends.server.protocols.ldap.LDAPAttribute;
037import org.opends.server.protocols.ldap.LDAPModification;
038import org.opends.server.protocols.ldap.LDAPResultCode;
039import org.opends.server.types.*;
040import org.opends.server.types.operation.PostResponseModifyOperation;
041import org.opends.server.types.operation.PreParseModifyOperation;
042import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
043
044import static org.opends.messages.CoreMessages.*;
045import static org.opends.server.core.DirectoryServer.*;
046import static org.opends.server.loggers.AccessLogger.*;
047import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*;
048
049/**
050 * This class defines an operation that may be used to modify an entry in the
051 * Directory Server.
052 */
053public class ModifyOperationBasis
054       extends AbstractOperation implements ModifyOperation,
055       PreParseModifyOperation,
056       PostResponseModifyOperation
057{
058  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
059
060  /** The raw, unprocessed entry DN as included by the client request. */
061  private ByteString rawEntryDN;
062
063  /** The DN of the entry for the modify operation. */
064  private DN entryDN;
065
066  /** The proxied authorization target DN for this operation. */
067  private DN proxiedAuthorizationDN;
068
069  /** The set of response controls for this modify operation. */
070  private List<Control> responseControls;
071
072  /** The raw, unprocessed set of modifications as included in the client request. */
073  private List<RawModification> rawModifications;
074
075  /** The set of modifications for this modify operation. */
076  private List<Modification> modifications;
077
078  /**
079   * Creates a new modify operation with the provided information.
080   *
081   * @param  clientConnection  The client connection with which this operation
082   *                           is associated.
083   * @param  operationID       The operation ID for this operation.
084   * @param  messageID         The message ID of the request with which this
085   *                           operation is associated.
086   * @param  requestControls   The set of controls included in the request.
087   * @param  rawEntryDN        The raw, unprocessed DN of the entry to modify,
088   *                           as included in the client request.
089   * @param  rawModifications  The raw, unprocessed set of modifications for
090   *                           this modify operation as included in the client
091   *                           request.
092   */
093  public ModifyOperationBasis(ClientConnection clientConnection,
094      long operationID,
095      int messageID, List<Control> requestControls,
096      ByteString rawEntryDN,
097      List<RawModification> rawModifications)
098  {
099    super(clientConnection, operationID, messageID, requestControls);
100
101
102    this.rawEntryDN       = rawEntryDN;
103    this.rawModifications = rawModifications;
104
105    entryDN          = null;
106    modifications    = null;
107    responseControls = new ArrayList<>();
108    cancelRequest    = null;
109  }
110
111  /**
112   * Creates a new modify operation with the provided information.
113   *
114   * @param  clientConnection  The client connection with which this operation
115   *                           is associated.
116   * @param  operationID       The operation ID for this operation.
117   * @param  messageID         The message ID of the request with which this
118   *                           operation is associated.
119   * @param  requestControls   The set of controls included in the request.
120   * @param  entryDN           The entry DN for the modify operation.
121   * @param  modifications     The set of modifications for this modify
122   *                           operation.
123   */
124  public ModifyOperationBasis(ClientConnection clientConnection,
125      long operationID,
126      int messageID, List<Control> requestControls,
127      DN entryDN, List<Modification> modifications)
128  {
129    super(clientConnection, operationID, messageID, requestControls);
130
131
132    this.entryDN       = entryDN;
133    this.modifications = modifications;
134
135    rawEntryDN = ByteString.valueOfUtf8(entryDN.toString());
136
137    rawModifications = new ArrayList<>(modifications.size());
138    for (Modification m : modifications)
139    {
140      rawModifications.add(new LDAPModification(m.getModificationType(),
141          new LDAPAttribute(m.getAttribute())));
142    }
143
144    responseControls = new ArrayList<>();
145    cancelRequest    = null;
146  }
147
148  /** {@inheritDoc} */
149  @Override
150  public final ByteString getRawEntryDN()
151  {
152    return rawEntryDN;
153  }
154
155  /** {@inheritDoc} */
156  @Override
157  public final void setRawEntryDN(ByteString rawEntryDN)
158  {
159    this.rawEntryDN = rawEntryDN;
160
161    entryDN = null;
162  }
163
164  /** {@inheritDoc} */
165  @Override
166  public final DN getEntryDN()
167  {
168    if (entryDN == null){
169      try {
170        entryDN = DN.decode(rawEntryDN);
171      }
172      catch (DirectoryException de) {
173        logger.traceException(de);
174
175        setResultCode(de.getResultCode());
176        appendErrorMessage(de.getMessageObject());
177      }
178    }
179    return entryDN;
180  }
181
182  /** {@inheritDoc} */
183  @Override
184  public final List<RawModification> getRawModifications()
185  {
186    return rawModifications;
187  }
188
189  /** {@inheritDoc} */
190  @Override
191  public final void addRawModification(RawModification rawModification)
192  {
193    rawModifications.add(rawModification);
194
195    modifications = null;
196  }
197
198  /** {@inheritDoc} */
199  @Override
200  public final void setRawModifications(List<RawModification> rawModifications)
201  {
202    this.rawModifications = rawModifications;
203
204    modifications = null;
205  }
206
207  /** {@inheritDoc} */
208  @Override
209  public final List<Modification> getModifications()
210  {
211    if (modifications == null)
212    {
213      modifications = new ArrayList<>(rawModifications.size());
214      try {
215        for (RawModification m : rawModifications)
216        {
217           Modification mod = m.toModification();
218           Attribute attr = mod.getAttribute();
219           AttributeType type = attr.getAttributeType();
220
221           if(type.getSyntax().isBEREncodingRequired())
222           {
223             if(!attr.hasOption("binary"))
224             {
225               //A binary option wasn't provided by the client so add it.
226               AttributeBuilder builder = new AttributeBuilder(attr);
227               builder.setOption("binary");
228               attr = builder.toAttribute();
229               mod.setAttribute(attr);
230             }
231           }
232           else if (attr.hasOption("binary"))
233           {
234             // binary option is not honored for non-BER-encodable attributes.
235             throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE,
236                 ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attr.getName()));
237           }
238
239           modifications.add(mod);
240        }
241      }
242      catch (LDAPException le)
243      {
244        logger.traceException(le);
245        setResultCode(ResultCode.valueOf(le.getResultCode()));
246        appendErrorMessage(le.getMessageObject());
247        modifications = null;
248      }
249    }
250    return modifications;
251  }
252
253  /** {@inheritDoc} */
254  @Override
255  public final void addModification(Modification modification)
256  throws DirectoryException
257  {
258    modifications.add(modification);
259  }
260
261  /** {@inheritDoc} */
262  @Override
263  public final OperationType getOperationType()
264  {
265    // Note that no debugging will be done in this method because it is a likely
266    // candidate for being called by the logging subsystem.
267
268    return OperationType.MODIFY;
269  }
270
271  /** {@inheritDoc} */
272  @Override
273  public DN getProxiedAuthorizationDN()
274  {
275    return proxiedAuthorizationDN;
276  }
277
278  /** {@inheritDoc} */
279  @Override
280  public final List<Control> getResponseControls()
281  {
282    return responseControls;
283  }
284
285  /** {@inheritDoc} */
286  @Override
287  public final void addResponseControl(Control control)
288  {
289    responseControls.add(control);
290  }
291
292  /** {@inheritDoc} */
293  @Override
294  public final void removeResponseControl(Control control)
295  {
296    responseControls.remove(control);
297  }
298
299  /** {@inheritDoc} */
300  @Override
301  public final void toString(StringBuilder buffer)
302  {
303    buffer.append("ModifyOperation(connID=");
304    buffer.append(clientConnection.getConnectionID());
305    buffer.append(", opID=");
306    buffer.append(operationID);
307    buffer.append(", dn=");
308    buffer.append(rawEntryDN);
309    buffer.append(")");
310  }
311
312  /** {@inheritDoc} */
313  @Override
314  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
315  {
316    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
317  }
318
319  /** {@inheritDoc} */
320  @Override
321  public final void run()
322  {
323    setResultCode(ResultCode.UNDEFINED);
324
325    // Start the processing timer.
326    setProcessingStartTime();
327
328    logModifyRequest(this);
329
330    // This flag is set to true as soon as a workflow has been executed.
331    boolean workflowExecuted = false;
332    try
333    {
334      // Check for and handle a request to cancel this operation.
335      checkIfCanceled(false);
336
337      // Invoke the pre-parse modify plugins.
338      if (!processOperationResult(getPluginConfigManager().invokePreParseModifyPlugins(this)))
339      {
340        return;
341      }
342
343      // Check for and handle a request to cancel this operation.
344      checkIfCanceled(false);
345
346
347      // Process the entry DN to convert it from the raw form to the form
348      // required for the rest of the modify processing.
349      DN entryDN = getEntryDN();
350      if (entryDN == null){
351        return;
352      }
353
354      workflowExecuted = execute(this, entryDN);
355    }
356    catch(CanceledOperationException coe)
357    {
358      logger.traceException(coe);
359
360      setResultCode(ResultCode.CANCELLED);
361      cancelResult = new CancelResult(ResultCode.CANCELLED, null);
362
363      appendErrorMessage(coe.getCancelRequest().getCancelReason());
364    }
365    finally
366    {
367      // Stop the processing timer.
368      setProcessingStopTime();
369
370      // Log the modify response.
371      logModifyResponse(this);
372
373      if(cancelRequest == null || cancelResult == null ||
374          cancelResult.getResultCode() != ResultCode.CANCELLED ||
375          cancelRequest.notifyOriginalRequestor() ||
376          DirectoryServer.notifyAbandonedOperations())
377      {
378        clientConnection.sendResponse(this);
379      }
380
381      // Invoke the post-response callbacks.
382      if (workflowExecuted) {
383        invokePostResponseCallbacks();
384      }
385
386      // Invoke the post-response add plugins.
387      invokePostResponsePlugins(workflowExecuted);
388
389      // If no cancel result, set it
390      if(cancelResult == null)
391      {
392        cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
393      }
394    }
395  }
396
397
398  /**
399   * Invokes the post response plugins. If a workflow has been executed
400   * then invoke the post response plugins provided by the workflow
401   * elements of the workflow, otherwise invoke the post response plugins
402   * that have been registered with the current operation.
403   *
404   * @param workflowExecuted <code>true</code> if a workflow has been executed
405   */
406  private void invokePostResponsePlugins(boolean workflowExecuted)
407  {
408    // Invoke the post response plugins
409    if (workflowExecuted)
410    {
411      // Invoke the post response plugins that have been registered by
412      // the workflow elements
413      @SuppressWarnings("unchecked")
414      List<LocalBackendModifyOperation> localOperations =
415          (List<LocalBackendModifyOperation>) getAttachment(
416              Operation.LOCALBACKENDOPERATIONS);
417      if (localOperations != null)
418      {
419        for (LocalBackendModifyOperation localOperation : localOperations)
420        {
421          getPluginConfigManager().invokePostResponseModifyPlugins(localOperation);
422        }
423      }
424    }
425    else
426    {
427      // Invoke the post response plugins that have been registered with
428      // the current operation
429      getPluginConfigManager().invokePostResponseModifyPlugins(this);
430    }
431  }
432
433  /** {@inheritDoc} */
434  @Override
435  public void updateOperationErrMsgAndResCode()
436  {
437    setResultCode(ResultCode.NO_SUCH_OBJECT);
438    appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(getEntryDN()));
439  }
440
441
442  /**
443   * {@inheritDoc}
444   *
445   * This method always returns null.
446   */
447  @Override
448  public Entry getCurrentEntry() {
449    return null;
450  }
451
452  /**
453   * {@inheritDoc}
454   *
455   * This method always returns null.
456   */
457  @Override
458  public List<ByteString> getCurrentPasswords()
459  {
460    return null;
461  }
462
463  /**
464   * {@inheritDoc}
465   *
466   * This method always returns null.
467   */
468  @Override
469  public Entry getModifiedEntry()
470  {
471    return null;
472  }
473
474  /**
475   * {@inheritDoc}
476   *
477   * This method always returns null.
478   */
479  @Override
480  public List<ByteString> getNewPasswords()
481  {
482    return null;
483  }
484
485}
486