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