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 2007-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.core;
028
029 import org.opends.messages.MessageBuilder;
030 import org.opends.messages.Message;
031
032
033 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
034 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
035 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
036 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
037 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
038 import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
039 import static org.opends.server.loggers.AccessLogger.logModifyRequest;
040 import static org.opends.server.loggers.AccessLogger.logModifyResponse;
041 import static org.opends.server.loggers.ErrorLogger.logError;
042 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
043 import static org.opends.messages.CoreMessages.*;
044 import static org.opends.server.util.StaticUtils.getExceptionMessage;
045
046 import java.util.ArrayList;
047 import java.util.Iterator;
048 import java.util.List;
049
050 import org.opends.server.api.ClientConnection;
051 import org.opends.server.api.plugin.PluginResult;
052 import org.opends.server.loggers.debug.DebugLogger;
053 import org.opends.server.loggers.debug.DebugTracer;
054 import org.opends.server.protocols.asn1.ASN1OctetString;
055 import org.opends.server.protocols.ldap.LDAPAttribute;
056 import org.opends.server.protocols.ldap.LDAPModification;
057 import org.opends.server.types.*;
058 import org.opends.server.types.operation.PostResponseModifyOperation;
059 import org.opends.server.types.operation.PreParseModifyOperation;
060 import org.opends.server.workflowelement.localbackend.*;
061
062
063
064 /**
065 * This class defines an operation that may be used to modify an entry in the
066 * Directory Server.
067 */
068 public class ModifyOperationBasis
069 extends AbstractOperation implements ModifyOperation,
070 PreParseModifyOperation,
071 PostResponseModifyOperation
072 {
073
074 /**
075 * The tracer object for the debug logger.
076 */
077 private static final DebugTracer TRACER = DebugLogger.getTracer();
078
079 // The raw, unprocessed entry DN as included by the client request.
080 private ByteString rawEntryDN;
081
082 // The DN of the entry for the modify operation.
083 private DN entryDN;
084
085 // The proxied authorization target DN for this operation.
086 private DN proxiedAuthorizationDN;
087
088 // The set of response controls for this modify operation.
089 private List<Control> responseControls;
090
091 // The raw, unprocessed set of modifications as included in the client
092 // request.
093 private List<RawModification> rawModifications;
094
095 // The set of modifications for this modify operation.
096 private List<Modification> modifications;
097
098 // The change number that has been assigned to this operation.
099 private long changeNumber;
100
101 /**
102 * Creates a new modify operation with the provided information.
103 *
104 * @param clientConnection The client connection with which this operation
105 * is associated.
106 * @param operationID The operation ID for this operation.
107 * @param messageID The message ID of the request with which this
108 * operation is associated.
109 * @param requestControls The set of controls included in the request.
110 * @param rawEntryDN The raw, unprocessed DN of the entry to modify,
111 * as included in the client request.
112 * @param rawModifications The raw, unprocessed set of modifications for
113 * this modify operation as included in the client
114 * request.
115 */
116 public ModifyOperationBasis(ClientConnection clientConnection,
117 long operationID,
118 int messageID, List<Control> requestControls,
119 ByteString rawEntryDN,
120 List<RawModification> rawModifications)
121 {
122 super(clientConnection, operationID, messageID, requestControls);
123
124
125 this.rawEntryDN = rawEntryDN;
126 this.rawModifications = rawModifications;
127
128 entryDN = null;
129 modifications = null;
130 responseControls = new ArrayList<Control>();
131 cancelRequest = null;
132 }
133
134 /**
135 * Creates a new modify operation with the provided information.
136 *
137 * @param clientConnection The client connection with which this operation
138 * is associated.
139 * @param operationID The operation ID for this operation.
140 * @param messageID The message ID of the request with which this
141 * operation is associated.
142 * @param requestControls The set of controls included in the request.
143 * @param entryDN The entry DN for the modify operation.
144 * @param modifications The set of modifications for this modify
145 * operation.
146 */
147 public ModifyOperationBasis(ClientConnection clientConnection,
148 long operationID,
149 int messageID, List<Control> requestControls,
150 DN entryDN, List<Modification> modifications)
151 {
152 super(clientConnection, operationID, messageID, requestControls);
153
154
155 this.entryDN = entryDN;
156 this.modifications = modifications;
157
158 rawEntryDN = new ASN1OctetString(entryDN.toString());
159
160 rawModifications = new ArrayList<RawModification>(modifications.size());
161 for (Modification m : modifications)
162 {
163 rawModifications.add(new LDAPModification(m.getModificationType(),
164 new LDAPAttribute(m.getAttribute())));
165 }
166
167 responseControls = new ArrayList<Control>();
168 cancelRequest = null;
169 }
170
171 /**
172 * {@inheritDoc}
173 */
174 public final ByteString getRawEntryDN()
175 {
176 return rawEntryDN;
177 }
178
179 /**
180 * {@inheritDoc}
181 */
182 public final void setRawEntryDN(ByteString rawEntryDN)
183 {
184 this.rawEntryDN = rawEntryDN;
185
186 entryDN = null;
187 }
188
189 /**
190 * {@inheritDoc}
191 */
192 public final DN getEntryDN()
193 {
194 if (entryDN == null){
195 try {
196 entryDN = DN.decode(rawEntryDN);
197 }
198 catch (DirectoryException de) {
199 if (debugEnabled())
200 {
201 TRACER.debugCaught(DebugLogLevel.ERROR, de);
202 }
203
204 setResultCode(de.getResultCode());
205 appendErrorMessage(de.getMessageObject());
206 }
207 }
208 return entryDN;
209 }
210
211 /**
212 * {@inheritDoc}
213 */
214 public final List<RawModification> getRawModifications()
215 {
216 return rawModifications;
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 public final void addRawModification(RawModification rawModification)
223 {
224 rawModifications.add(rawModification);
225
226 modifications = null;
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 public final void setRawModifications(List<RawModification> rawModifications)
233 {
234 this.rawModifications = rawModifications;
235
236 modifications = null;
237 }
238
239 /**
240 * {@inheritDoc}
241 */
242 public final List<Modification> getModifications()
243 {
244 if (modifications == null)
245 {
246 modifications = new ArrayList<Modification>(rawModifications.size());
247 try {
248 for (RawModification m : rawModifications)
249 {
250 modifications.add(m.toModification());
251 }
252 }
253 catch (LDAPException le)
254 {
255 if (debugEnabled())
256 {
257 TRACER.debugCaught(DebugLogLevel.ERROR, le);
258 }
259 setResultCode(ResultCode.valueOf(le.getResultCode()));
260 appendErrorMessage(le.getMessageObject());
261 modifications = null;
262 }
263 }
264 return modifications;
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 public final void addModification(Modification modification)
271 throws DirectoryException
272 {
273 modifications.add(modification);
274 }
275
276 /**
277 * {@inheritDoc}
278 */
279 public final OperationType getOperationType()
280 {
281 // Note that no debugging will be done in this method because it is a likely
282 // candidate for being called by the logging subsystem.
283
284 return OperationType.MODIFY;
285 }
286
287 /**
288 * {@inheritDoc}
289 */
290 public final String[][] getRequestLogElements()
291 {
292 // Note that no debugging will be done in this method because it is a likely
293 // candidate for being called by the logging subsystem.
294
295 return new String[][]
296 {
297 new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
298 };
299 }
300
301 /**
302 * {@inheritDoc}
303 */
304 public final String[][] getResponseLogElements()
305 {
306 // Note that no debugging will be done in this method because it is a likely
307 // candidate for being called by the logging subsystem.
308
309 String resultCode = String.valueOf(getResultCode().getIntValue());
310
311 String errorMessage;
312 MessageBuilder errorMessageBuffer = getErrorMessage();
313 if (errorMessageBuffer == null)
314 {
315 errorMessage = null;
316 }
317 else
318 {
319 errorMessage = errorMessageBuffer.toString();
320 }
321
322 String matchedDNStr;
323 DN matchedDN = getMatchedDN();
324 if (matchedDN == null)
325 {
326 matchedDNStr = null;
327 }
328 else
329 {
330 matchedDNStr = matchedDN.toString();
331 }
332
333 String referrals;
334 List<String> referralURLs = getReferralURLs();
335 if ((referralURLs == null) || referralURLs.isEmpty())
336 {
337 referrals = null;
338 }
339 else
340 {
341 StringBuilder buffer = new StringBuilder();
342 Iterator<String> iterator = referralURLs.iterator();
343 buffer.append(iterator.next());
344
345 while (iterator.hasNext())
346 {
347 buffer.append(", ");
348 buffer.append(iterator.next());
349 }
350
351 referrals = buffer.toString();
352 }
353
354 String processingTime =
355 String.valueOf(getProcessingTime());
356
357 return new String[][]
358 {
359 new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
360 new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
361 new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
362 new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
363 new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
364 };
365 }
366
367 /**
368 * {@inheritDoc}
369 */
370 public DN getProxiedAuthorizationDN()
371 {
372 return proxiedAuthorizationDN;
373 }
374
375 /**
376 * {@inheritDoc}
377 */
378 public final List<Control> getResponseControls()
379 {
380 return responseControls;
381 }
382
383 /**
384 * {@inheritDoc}
385 */
386 public final void addResponseControl(Control control)
387 {
388 responseControls.add(control);
389 }
390
391 /**
392 * {@inheritDoc}
393 */
394 public final void removeResponseControl(Control control)
395 {
396 responseControls.remove(control);
397 }
398
399 /**
400 * {@inheritDoc}
401 */
402 public final void toString(StringBuilder buffer)
403 {
404 buffer.append("ModifyOperation(connID=");
405 buffer.append(clientConnection.getConnectionID());
406 buffer.append(", opID=");
407 buffer.append(operationID);
408 buffer.append(", dn=");
409 buffer.append(rawEntryDN);
410 buffer.append(")");
411 }
412
413 /**
414 * {@inheritDoc}
415 */
416 public final long getChangeNumber(){
417 return changeNumber;
418 }
419
420 /**
421 * {@inheritDoc}
422 */
423 public void setChangeNumber(long changeNumber)
424 {
425 this.changeNumber = changeNumber;
426 }
427
428 /**
429 * {@inheritDoc}
430 */
431 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
432 {
433 this.proxiedAuthorizationDN = proxiedAuthorizationDN;
434 }
435
436 /**
437 * {@inheritDoc}
438 */
439 public final void run()
440 {
441 setResultCode(ResultCode.UNDEFINED);
442
443 // Start the processing timer.
444 setProcessingStartTime();
445
446 // Log the modify request message.
447 logModifyRequest(this);
448
449 // Get the plugin config manager that will be used for invoking plugins.
450 PluginConfigManager pluginConfigManager =
451 DirectoryServer.getPluginConfigManager();
452
453 // This flag is set to true as soon as a workflow has been executed.
454 boolean workflowExecuted = false;
455
456
457 try
458 {
459 // Check for and handle a request to cancel this operation.
460 checkIfCanceled(false);
461
462 // Invoke the pre-parse modify plugins.
463 PluginResult.PreParse preParseResult =
464 pluginConfigManager.invokePreParseModifyPlugins(this);
465
466 if(!preParseResult.continueProcessing())
467 {
468 setResultCode(preParseResult.getResultCode());
469 appendErrorMessage(preParseResult.getErrorMessage());
470 setMatchedDN(preParseResult.getMatchedDN());
471 setReferralURLs(preParseResult.getReferralURLs());
472 return;
473 }
474
475 // Check for and handle a request to cancel this operation.
476 checkIfCanceled(false);
477
478
479 // Process the entry DN to convert it from the raw form to the form
480 // required for the rest of the modify processing.
481 DN entryDN = getEntryDN();
482 if (entryDN == null){
483 return;
484 }
485
486
487 // Retrieve the network group attached to the client connection
488 // and get a workflow to process the operation.
489 NetworkGroup ng = getClientConnection().getNetworkGroup();
490 Workflow workflow = ng.getWorkflowCandidate(entryDN);
491 if (workflow == null)
492 {
493 // We have found no workflow for the requested base DN, just return
494 // a no such entry result code and stop the processing.
495 updateOperationErrMsgAndResCode();
496 return;
497 }
498 workflow.execute(this);
499 workflowExecuted = true;
500
501 }
502 catch(CanceledOperationException coe)
503 {
504 if (debugEnabled())
505 {
506 TRACER.debugCaught(DebugLogLevel.ERROR, coe);
507 }
508
509 setResultCode(ResultCode.CANCELED);
510 cancelResult = new CancelResult(ResultCode.CANCELED, null);
511
512 appendErrorMessage(coe.getCancelRequest().getCancelReason());
513 }
514 finally
515 {
516 // Stop the processing timer.
517 setProcessingStopTime();
518
519 if(cancelRequest == null || cancelResult == null ||
520 cancelResult.getResultCode() != ResultCode.CANCELED ||
521 cancelRequest.notifyOriginalRequestor() ||
522 DirectoryServer.notifyAbandonedOperations())
523 {
524 clientConnection.sendResponse(this);
525 }
526
527 // Log the modify response.
528 logModifyResponse(this);
529
530 // Notifies any persistent searches that might be registered with the
531 // server.
532 notifyPersistentSearches(workflowExecuted);
533
534 // Invoke the post-response add plugins.
535 invokePostResponsePlugins(workflowExecuted);
536
537 // If no cancel result, set it
538 if(cancelResult == null)
539 {
540 cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
541 }
542 }
543 }
544
545
546 /**
547 * Invokes the post response plugins. If a workflow has been executed
548 * then invoke the post response plugins provided by the workflow
549 * elements of the worklfow, otherwise invoke the post reponse plugins
550 * that have been registered with the current operation.
551 *
552 * @param workflowExecuted <code>true</code> if a workflow has been
553 * executed
554 */
555 private void invokePostResponsePlugins(boolean workflowExecuted)
556 {
557 // Get the plugin config manager that will be used for invoking plugins.
558 PluginConfigManager pluginConfigManager =
559 DirectoryServer.getPluginConfigManager();
560
561 // Invoke the post response plugins
562 if (workflowExecuted)
563 {
564 // Invoke the post response plugins that have been registered by
565 // the workflow elements
566 List localOperations =
567 (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
568
569 if (localOperations != null)
570 {
571 for (Object localOp : localOperations)
572 {
573 LocalBackendModifyOperation localOperation =
574 (LocalBackendModifyOperation)localOp;
575 pluginConfigManager.invokePostResponseModifyPlugins(localOperation);
576 }
577 }
578 }
579 else
580 {
581 // Invoke the post response plugins that have been registered with
582 // the current operation
583 pluginConfigManager.invokePostResponseModifyPlugins(this);
584 }
585 }
586
587
588 /**
589 * Notifies any persistent searches that might be registered with the server.
590 * If no workflow has been executed then don't notify persistent searches.
591 *
592 * @param workflowExecuted <code>true</code> if a workflow has been
593 * executed
594 */
595 private void notifyPersistentSearches(boolean workflowExecuted)
596 {
597 if (! workflowExecuted)
598 {
599 return;
600 }
601
602 List localOperations =
603 (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
604
605 if (localOperations != null)
606 {
607 for (Object localOp : localOperations)
608 {
609 LocalBackendModifyOperation localOperation =
610 (LocalBackendModifyOperation)localOp;
611 // Notify any persistent searches that might be registered with
612 // the server.
613 if (localOperation.getResultCode() == ResultCode.SUCCESS)
614 {
615 for (PersistentSearch persistentSearch :
616 DirectoryServer.getPersistentSearches())
617 {
618 try
619 {
620 persistentSearch.processModify(localOperation,
621 localOperation.getCurrentEntry(),
622 localOperation.getModifiedEntry());
623 }
624 catch (Exception e)
625 {
626 if (debugEnabled())
627 {
628 TRACER.debugCaught(DebugLogLevel.ERROR, e);
629 }
630
631 Message message = ERR_MODIFY_ERROR_NOTIFYING_PERSISTENT_SEARCH.
632 get(String.valueOf(persistentSearch), getExceptionMessage(e));
633 logError(message);
634
635 DirectoryServer.deregisterPersistentSearch(persistentSearch);
636 }
637 }
638 }
639 }
640 }
641 }
642
643
644 /**
645 * Updates the error message and the result code of the operation.
646 *
647 * This method is called because no workflows were found to process
648 * the operation.
649 */
650 private void updateOperationErrMsgAndResCode()
651 {
652 setResultCode(ResultCode.NO_SUCH_OBJECT);
653 appendErrorMessage(
654 ERR_MODIFY_NO_SUCH_ENTRY.get(String.valueOf(getEntryDN())));
655 }
656
657
658 /**
659 * {@inheritDoc}
660 *
661 * This method always returns null.
662 */
663 public Entry getCurrentEntry() {
664 // TODO Auto-generated method stub
665 return null;
666 }
667
668 /**
669 * {@inheritDoc}
670 *
671 * This method always returns null.
672 */
673 public List<AttributeValue> getCurrentPasswords()
674 {
675 return null;
676 }
677
678 /**
679 * {@inheritDoc}
680 *
681 * This method always returns null.
682 */
683 public Entry getModifiedEntry()
684 {
685 return null;
686 }
687
688 /**
689 * {@inheritDoc}
690 *
691 * This method always returns null.
692 */
693 public List<AttributeValue> getNewPasswords()
694 {
695 return null;
696 }
697
698 }
699