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