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