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