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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    import org.opends.messages.MessageBuilder;
030    
031    import java.util.ArrayList;
032    import java.util.Iterator;
033    import java.util.List;
034    import org.opends.server.api.ClientConnection;
035    import org.opends.server.api.plugin.PluginResult;
036    import org.opends.server.protocols.asn1.ASN1OctetString;
037    import org.opends.server.types.operation.PostResponseModifyDNOperation;
038    import org.opends.server.types.operation.PreParseModifyDNOperation;
039    import static org.opends.server.core.CoreConstants.*;
040    import static org.opends.server.loggers.AccessLogger.*;
041    
042    import org.opends.server.types.*;
043    import org.opends.server.workflowelement.localbackend.*;
044    
045    import static org.opends.server.loggers.debug.DebugLogger.*;
046    
047    import org.opends.server.loggers.debug.DebugLogger;
048    import org.opends.server.loggers.debug.DebugTracer;
049    import org.opends.server.loggers.ErrorLogger;
050    import static org.opends.messages.CoreMessages.*;
051    import static org.opends.server.util.StaticUtils.*;
052    
053    
054    /**
055     * This class defines an operation that may be used to alter the DN of an entry
056     * in the Directory Server.
057     */
058    public class ModifyDNOperationBasis
059           extends AbstractOperation
060           implements ModifyDNOperation,
061                      PreParseModifyDNOperation,
062                      PostResponseModifyDNOperation
063                      {
064      /**
065       * The tracer object for the debug logger.
066       */
067      private static final DebugTracer TRACER = DebugLogger.getTracer();
068    
069      // Indicates whether to delete the old RDN value from the entry.
070      private boolean deleteOldRDN;
071    
072      // The raw, unprocessed current DN of the entry as included in the request
073      // from the client.
074      private ByteString rawEntryDN;
075    
076      // The raw, unprocessed newRDN as included in the request from the client.
077      private ByteString rawNewRDN;
078    
079      // The raw, unprocessed newSuperior as included in the request from the
080      // client.
081      private ByteString rawNewSuperior;
082    
083      // The current DN of the entry.
084      private DN entryDN;
085    
086      // The new parent for the entry.
087      private DN newSuperior;
088    
089      // The proxied authorization target DN for this operation.
090      private DN proxiedAuthorizationDN;
091    
092      // The set of response controls for this modify DN operation.
093      private List<Control> responseControls;
094    
095      // The set of modifications applied to attributes in the entry in the course
096      // of processing the modify DN.
097      private List<Modification> modifications;
098    
099      // The change number that has been assigned to this operation.
100      private long changeNumber;
101    
102      // The new RDN for the entry.
103      private RDN newRDN;
104    
105      // The new entry DN
106      private DN newDN = null;
107    
108      /**
109       * Creates a new modify DN operation with the provided information.
110       *
111       * @param  clientConnection  The client connection with which this operation
112       *                           is associated.
113       * @param  operationID       The operation ID for this operation.
114       * @param  messageID         The message ID of the request with which this
115       *                           operation is associated.
116       * @param  requestControls   The set of controls included in the request.
117       * @param  rawEntryDN        The raw, unprocessed entry DN as included in the
118       *                           client request.
119       * @param  rawNewRDN         The raw, unprocessed newRDN as included in the
120       *                           client request.
121       * @param  deleteOldRDN      Indicates whether to delete the old RDN value
122       *                           from the entry.
123       * @param  rawNewSuperior    The raw, unprocessed newSuperior as included in
124       *                           the client request.
125       */
126      public ModifyDNOperationBasis(ClientConnection clientConnection,
127          long operationID,
128          int messageID, List<Control> requestControls,
129          ByteString rawEntryDN, ByteString rawNewRDN,
130          boolean deleteOldRDN, ByteString rawNewSuperior)
131      {
132        super(clientConnection, operationID, messageID, requestControls);
133    
134    
135        this.rawEntryDN      = rawEntryDN;
136        this.rawNewRDN       = rawNewRDN;
137        this.deleteOldRDN    = deleteOldRDN;
138        this.rawNewSuperior  = rawNewSuperior;
139    
140        entryDN          = null;
141        newRDN           = null;
142        newSuperior      = null;
143        responseControls = new ArrayList<Control>();
144        cancelRequest    = null;
145        modifications    = null;
146        changeNumber     = -1;
147      }
148    
149    
150    
151      /**
152       * Creates a new modify DN operation with the provided information.
153       *
154       * @param  clientConnection  The client connection with which this operation
155       *                           is associated.
156       * @param  operationID       The operation ID for this operation.
157       * @param  messageID         The message ID of the request with which this
158       *                           operation is associated.
159       * @param  requestControls   The set of controls included in the request.
160       * @param  entryDN           The current entry DN for this modify DN
161       *                           operation.
162       * @param  newRDN            The new RDN for this modify DN operation.
163       * @param  deleteOldRDN      Indicates whether to delete the old RDN value
164       *                           from the entry.
165       * @param  newSuperior       The newSuperior DN for this modify DN operation.
166       */
167      public ModifyDNOperationBasis(ClientConnection clientConnection,
168          long operationID,
169          int messageID, List<Control> requestControls,
170          DN entryDN, RDN newRDN, boolean deleteOldRDN,
171          DN newSuperior)
172      {
173        super(clientConnection, operationID, messageID, requestControls);
174    
175    
176        this.entryDN      = entryDN;
177        this.newRDN       = newRDN;
178        this.deleteOldRDN = deleteOldRDN;
179        this.newSuperior  = newSuperior;
180    
181        rawEntryDN = new ASN1OctetString(entryDN.toString());
182        rawNewRDN  = new ASN1OctetString(newRDN.toString());
183    
184        if (newSuperior == null)
185        {
186          rawNewSuperior = null;
187        }
188        else
189        {
190          rawNewSuperior = new ASN1OctetString(newSuperior.toString());
191        }
192    
193        responseControls = new ArrayList<Control>();
194        cancelRequest    = null;
195        modifications    = null;
196        changeNumber     = -1;
197      }
198    
199    
200    
201      /**
202       * {@inheritDoc}
203       */
204      public final ByteString getRawEntryDN()
205      {
206        return rawEntryDN;
207      }
208    
209    
210    
211      /**
212       * {@inheritDoc}
213       */
214      public final void setRawEntryDN(ByteString rawEntryDN)
215      {
216        this.rawEntryDN = rawEntryDN;
217    
218        entryDN = null;
219      }
220    
221    
222    
223      /**
224       * {@inheritDoc}
225       */
226      public final DN getEntryDN()
227      {
228        try
229        {
230          if (entryDN == null)
231          {
232            entryDN = DN.decode(rawEntryDN);
233          }
234        }
235        catch (DirectoryException de)
236        {
237          if (debugEnabled())
238          {
239            TRACER.debugCaught(DebugLogLevel.ERROR, de);
240          }
241          setResultCode(de.getResultCode());
242          appendErrorMessage(de.getMessageObject());
243        }
244        return entryDN;
245      }
246    
247      /**
248       * {@inheritDoc}
249       */
250      public final ByteString getRawNewRDN()
251      {
252        return rawNewRDN;
253      }
254    
255      /**
256       * {@inheritDoc}
257       */
258      public final void setRawNewRDN(ByteString rawNewRDN)
259      {
260        this.rawNewRDN = rawNewRDN;
261    
262        newRDN = null;
263        newDN = null;
264      }
265    
266      /**
267       * {@inheritDoc}
268       */
269      public final RDN getNewRDN()
270      {
271        try
272        {
273          if (newRDN == null)
274          {
275            newRDN = RDN.decode(rawNewRDN.stringValue());
276          }
277        }
278        catch (DirectoryException de)
279        {
280          if (debugEnabled())
281          {
282            TRACER.debugCaught(DebugLogLevel.ERROR, de);
283          }
284    
285          setResultCode(de.getResultCode());
286          appendErrorMessage(de.getMessageObject());
287        }
288        return newRDN;
289      }
290    
291    
292      /**
293       * {@inheritDoc}
294       */
295      public final boolean deleteOldRDN()
296      {
297        return deleteOldRDN;
298      }
299    
300      /**
301       * {@inheritDoc}
302       */
303      public final void setDeleteOldRDN(boolean deleteOldRDN)
304      {
305        this.deleteOldRDN = deleteOldRDN;
306      }
307    
308      /**
309       * {@inheritDoc}
310       */
311      public final ByteString getRawNewSuperior()
312      {
313        return rawNewSuperior;
314      }
315    
316      /**
317       * {@inheritDoc}
318       */
319      public final void setRawNewSuperior(ByteString rawNewSuperior)
320      {
321        this.rawNewSuperior = rawNewSuperior;
322    
323        newSuperior = null;
324        newDN = null;
325      }
326    
327      /**
328       * {@inheritDoc}
329       */
330      public final DN getNewSuperior()
331      {
332        if (rawNewSuperior == null)
333        {
334          newSuperior = null;
335        }
336        else
337        {
338          try
339          {
340            if (newSuperior == null)
341            {
342              newSuperior = DN.decode(rawNewSuperior);
343            }
344          }
345          catch (DirectoryException de)
346          {
347            if (debugEnabled())
348            {
349              TRACER.debugCaught(DebugLogLevel.ERROR, de);
350            }
351    
352            setResultCode(de.getResultCode());
353            appendErrorMessage(de.getMessageObject());
354          }
355        }
356        return newSuperior;
357      }
358    
359    
360      /**
361       * {@inheritDoc}
362       */
363      public final List<Modification> getModifications()
364      {
365        return modifications;
366      }
367    
368    
369      /**
370       * {@inheritDoc}
371       */
372      public final void addModification(Modification modification)
373      {
374        if (modifications == null)
375        {
376          modifications = new ArrayList<Modification>();
377        }
378        if (modification != null)
379        {
380          modifications.add(modification);
381        }
382      }
383    
384    
385      /**
386       * {@inheritDoc}
387       */
388      public final Entry getOriginalEntry()
389      {
390        return null;
391      }
392    
393    
394      /**
395       * {@inheritDoc}
396       */
397      public final Entry getUpdatedEntry()
398      {
399        return null;
400      }
401    
402      /**
403       * {@inheritDoc}
404       */
405      public final long getChangeNumber()
406      {
407        return changeNumber;
408      }
409    
410    
411      /**
412       * {@inheritDoc}
413       */
414      public final void setChangeNumber(long changeNumber)
415      {
416        this.changeNumber = changeNumber;
417      }
418    
419    
420      /**
421       * {@inheritDoc}
422       */
423      @Override()
424      public final OperationType getOperationType()
425      {
426        // Note that no debugging will be done in this method because it is a likely
427        // candidate for being called by the logging subsystem.
428    
429        return OperationType.MODIFY_DN;
430      }
431    
432    
433      /**
434       * {@inheritDoc}
435       */
436      @Override()
437      public final String[][] getRequestLogElements()
438      {
439        // Note that no debugging will be done in this method because it is a likely
440        // candidate for being called by the logging subsystem.
441    
442        String newSuperiorStr;
443        if (rawNewSuperior == null)
444        {
445          newSuperiorStr = null;
446        }
447        else
448        {
449          newSuperiorStr = rawNewSuperior.stringValue();
450        }
451    
452        return new String[][]
453                            {
454            new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) },
455            new String[] { LOG_ELEMENT_NEW_RDN, String.valueOf(newRDN) },
456            new String[] { LOG_ELEMENT_DELETE_OLD_RDN,
457                String.valueOf(deleteOldRDN) },
458            new String[] { LOG_ELEMENT_NEW_SUPERIOR, newSuperiorStr }
459                            };
460      }
461    
462    
463      /**
464       * {@inheritDoc}
465       */
466      @Override()
467      public final String[][] getResponseLogElements()
468      {
469        // Note that no debugging will be done in this method because it is a likely
470        // candidate for being called by the logging subsystem.
471    
472        String resultCode = String.valueOf(getResultCode().getIntValue());
473    
474        String errorMessage;
475        MessageBuilder errorMessageBuffer = getErrorMessage();
476        if (errorMessageBuffer == null)
477        {
478          errorMessage = null;
479        }
480        else
481        {
482          errorMessage = errorMessageBuffer.toString();
483        }
484    
485        String matchedDNStr;
486        DN matchedDN = getMatchedDN();
487        if (matchedDN == null)
488        {
489          matchedDNStr = null;
490        }
491        else
492        {
493          matchedDNStr = matchedDN.toString();
494        }
495    
496        String referrals;
497        List<String> referralURLs = getReferralURLs();
498        if ((referralURLs == null) || referralURLs.isEmpty())
499        {
500          referrals = null;
501        }
502        else
503        {
504          StringBuilder buffer = new StringBuilder();
505          Iterator<String> iterator = referralURLs.iterator();
506          buffer.append(iterator.next());
507    
508          while (iterator.hasNext())
509          {
510            buffer.append(", ");
511            buffer.append(iterator.next());
512          }
513    
514          referrals = buffer.toString();
515        }
516    
517        String processingTime =
518          String.valueOf(getProcessingTime());
519    
520        return new String[][]
521                            {
522            new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
523            new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
524            new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
525            new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
526            new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
527                            };
528      }
529    
530    
531      /**
532       * {@inheritDoc}
533       */
534      public DN getProxiedAuthorizationDN()
535      {
536        return proxiedAuthorizationDN;
537      }
538    
539    
540      /**
541       * {@inheritDoc}
542       */
543      @Override()
544      public final List<Control> getResponseControls()
545      {
546        return responseControls;
547      }
548    
549    
550      /**
551       * {@inheritDoc}
552       */
553      @Override()
554      public final void addResponseControl(Control control)
555      {
556        responseControls.add(control);
557      }
558    
559    
560      /**
561       * {@inheritDoc}
562       */
563      @Override()
564      public final void removeResponseControl(Control control)
565      {
566        responseControls.remove(control);
567      }
568    
569    
570      /**
571       * Performs the work of actually processing this operation.  This
572       * should include all processing for the operation, including
573       * invoking plugins, logging messages, performing access control,
574       * managing synchronization, and any other work that might need to
575       * be done in the course of processing.
576       */
577      public final void run()
578      {
579        setResultCode(ResultCode.UNDEFINED);
580    
581        // Start the processing timer.
582        setProcessingStartTime();
583    
584        // Log the modify DN request message.
585        logModifyDNRequest(this);
586    
587        // Get the plugin config manager that will be used for invoking plugins.
588        PluginConfigManager pluginConfigManager =
589            DirectoryServer.getPluginConfigManager();
590    
591        // This flag is set to true as soon as a workflow has been executed.
592        boolean workflowExecuted = false;
593    
594    
595        try
596        {
597          // Check for and handle a request to cancel this operation.
598          checkIfCanceled(false);
599    
600          // Invoke the pre-parse modify DN plugins.
601          PluginResult.PreParse preParseResult =
602              pluginConfigManager.invokePreParseModifyDNPlugins(this);
603    
604          if(!preParseResult.continueProcessing())
605          {
606            setResultCode(preParseResult.getResultCode());
607            appendErrorMessage(preParseResult.getErrorMessage());
608            setMatchedDN(preParseResult.getMatchedDN());
609            setReferralURLs(preParseResult.getReferralURLs());
610            return;
611          }
612    
613          // Check for and handle a request to cancel this operation.
614          checkIfCanceled(false);
615    
616          // Process the entry DN, newRDN, and newSuperior elements from their raw
617          // forms as provided by the client to the forms required for the rest of
618          // the modify DN processing.
619          DN entryDN = getEntryDN();
620          if (entryDN == null)
621          {
622            return;
623          }
624    
625          // Retrieve the network group attached to the client connection
626          // and get a workflow to process the operation.
627          NetworkGroup ng = getClientConnection().getNetworkGroup();
628          Workflow workflow = ng.getWorkflowCandidate(entryDN);
629          if (workflow == null)
630          {
631            // We have found no workflow for the requested base DN, just return
632            // a no such entry result code and stop the processing.
633            updateOperationErrMsgAndResCode();
634            return;
635          }
636          workflow.execute(this);
637          workflowExecuted = true;
638        }
639        catch(CanceledOperationException coe)
640        {
641          if (debugEnabled())
642          {
643            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
644          }
645    
646          setResultCode(ResultCode.CANCELED);
647          cancelResult = new CancelResult(ResultCode.CANCELED, null);
648    
649          appendErrorMessage(coe.getCancelRequest().getCancelReason());
650        }
651        finally
652        {
653          // Stop the processing timer.
654          setProcessingStopTime();
655    
656          if(cancelRequest == null || cancelResult == null ||
657              cancelResult.getResultCode() != ResultCode.CANCELED ||
658              cancelRequest.notifyOriginalRequestor() ||
659              DirectoryServer.notifyAbandonedOperations())
660          {
661            clientConnection.sendResponse(this);
662          }
663    
664          // Log the modify DN response.
665          logModifyDNResponse(this);
666    
667          // Notifies any persistent searches that might be registered with the
668          // server.
669          notifyPersistentSearches(workflowExecuted);
670    
671          // Invoke the post-response modify DN plugins.
672          invokePostResponsePlugins(workflowExecuted);
673    
674          // If no cancel result, set it
675          if(cancelResult == null)
676          {
677            cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
678          }
679        }
680      }
681    
682    
683      /**
684       * Invokes the post response plugins. If a workflow has been executed
685       * then invoke the post response plugins provided by the workflow
686       * elements of the worklfow, otherwise invoke the post reponse plugins
687       * that have been registered with the current operation.
688       *
689       * @param workflowExecuted <code>true</code> if a workflow has been
690       *                         executed
691       */
692      private void invokePostResponsePlugins(boolean workflowExecuted)
693      {
694        // Get the plugin config manager that will be used for invoking plugins.
695        PluginConfigManager pluginConfigManager =
696          DirectoryServer.getPluginConfigManager();
697    
698        // Invoke the post response plugins
699        if (workflowExecuted)
700        {
701          // Invoke the post response plugins that have been registered by
702          // the workflow elements
703          List localOperations =
704            (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
705    
706          if (localOperations != null)
707          {
708            for (Object localOp : localOperations)
709            {
710              LocalBackendModifyDNOperation localOperation =
711                (LocalBackendModifyDNOperation)localOp;
712              pluginConfigManager.invokePostResponseModifyDNPlugins(localOperation);
713            }
714          }
715        }
716        else
717        {
718          // Invoke the post response plugins that have been registered with
719          // the current operation
720          pluginConfigManager.invokePostResponseModifyDNPlugins(this);
721        }
722      }
723    
724    
725      /**
726       * Notifies any persistent searches that might be registered with the server.
727       * If no workflow has been executed then don't notify persistent searches.
728       *
729       * @param workflowExecuted <code>true</code> if a workflow has been
730       *                         executed
731       */
732      private void notifyPersistentSearches(boolean workflowExecuted)
733      {
734        if (! workflowExecuted)
735        {
736          return;
737        }
738    
739        List localOperations =
740          (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
741    
742        if (localOperations != null)
743        {
744          for (Object localOperation : localOperations)
745          {
746            LocalBackendModifyDNOperation localOp =
747              (LocalBackendModifyDNOperation)localOperation;
748            // Notify any persistent searches that might be registered with
749            // the server.
750            if (getResultCode() == ResultCode.SUCCESS)
751            {
752              for (PersistentSearch persistentSearch :
753                DirectoryServer.getPersistentSearches())
754              {
755                try
756                {
757                  persistentSearch.processModifyDN(
758                      localOp,
759                      localOp.getOriginalEntry(),
760                      localOp.getUpdatedEntry());
761                }
762                catch (Exception e)
763                {
764                  if (debugEnabled())
765                  {
766                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
767                  }
768    
769                  Message message = ERR_MODDN_ERROR_NOTIFYING_PERSISTENT_SEARCH.get(
770                      String.valueOf(persistentSearch), getExceptionMessage(e));
771                  ErrorLogger.logError(message);
772    
773                  DirectoryServer.deregisterPersistentSearch(persistentSearch);
774                }
775              }
776            }
777          }
778        }
779      }
780    
781    
782      /**
783       * Updates the error message and the result code of the operation.
784       *
785       * This method is called because no workflows were found to process
786       * the operation.
787       */
788      private void updateOperationErrMsgAndResCode()
789      {
790        setResultCode(ResultCode.NO_SUCH_OBJECT);
791        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
792                String.valueOf(entryDN)));
793      }
794    
795    
796      /**
797       * {@inheritDoc}
798       */
799      @Override()
800      public final void toString(StringBuilder buffer)
801      {
802        buffer.append("ModifyDNOperation(connID=");
803        buffer.append(clientConnection.getConnectionID());
804        buffer.append(", opID=");
805        buffer.append(operationID);
806        buffer.append(", dn=");
807        buffer.append(rawEntryDN);
808        buffer.append(", newRDN=");
809        buffer.append(rawNewRDN);
810        buffer.append(", deleteOldRDN=");
811        buffer.append(deleteOldRDN);
812    
813        if (rawNewSuperior != null)
814        {
815          buffer.append(", newSuperior=");
816          buffer.append(rawNewSuperior);
817        }
818        buffer.append(")");
819      }
820    
821    
822      /**
823       * {@inheritDoc}
824       */
825      public void setProxiedAuthorizationDN(DN dn)
826      {
827        proxiedAuthorizationDN = dn;
828      }
829    
830    
831      /**
832       * {@inheritDoc}
833       */
834      public DN getNewDN()
835      {
836        if (newDN == null)
837        {
838          // Construct the new DN to use for the entry.
839          DN parentDN = null;
840          if (newSuperior == null)
841          {
842            if (getEntryDN() != null)
843            {
844              parentDN = entryDN.getParentDNInSuffix();
845            }
846          }
847          else
848          {
849            parentDN = newSuperior;
850          }
851    
852          if ((parentDN == null) || parentDN.isNullDN())
853          {
854            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
855            appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
856          }
857          newDN = parentDN.concat(newRDN);
858        }
859        return newDN;
860      }
861    
862    }
863