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