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