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    import org.opends.messages.Message;
029    import org.opends.messages.MessageBuilder;
030    
031    
032    import static org.opends.server.config.ConfigConstants.ATTR_OBJECTCLASS;
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.logAddRequest;
040    import static org.opends.server.loggers.AccessLogger.logAddResponse;
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    import static org.opends.server.util.StaticUtils.toLowerCase;
046    
047    import java.util.ArrayList;
048    import java.util.HashMap;
049    import java.util.Iterator;
050    import java.util.LinkedHashSet;
051    import java.util.List;
052    import java.util.Map;
053    
054    import org.opends.server.api.ClientConnection;
055    import org.opends.server.api.plugin.PluginResult;
056    import org.opends.server.loggers.debug.DebugLogger;
057    import org.opends.server.loggers.debug.DebugTracer;
058    import org.opends.server.protocols.asn1.ASN1OctetString;
059    import org.opends.server.protocols.ldap.LDAPAttribute;
060    import org.opends.server.types.*;
061    import org.opends.server.types.operation.PostResponseAddOperation;
062    import org.opends.server.types.operation.PreParseAddOperation;
063    import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
064    
065    
066    
067    
068    /**
069     * This class defines an operation that may be used to add a new entry to the
070     * Directory Server.
071     */
072    public class AddOperationBasis
073           extends AbstractOperation
074           implements PreParseAddOperation, AddOperation, Runnable,
075                      PostResponseAddOperation
076    {
077    
078      /**
079       * The tracer object for the debug logger.
080       */
081      private static final DebugTracer TRACER = DebugLogger.getTracer();
082    
083      // The set of response controls to send to the client.
084      private ArrayList<Control> responseControls;
085    
086      // The raw, unprocessed entry DN as provided in the request.  This may or may
087      // not be a valid DN.
088      private ByteString rawEntryDN;
089    
090      // The processed DN of the entry to add.
091      private DN entryDN;
092    
093      // The proxied authorization target DN for this operation.
094      private DN proxiedAuthorizationDN;
095    
096      // The set of attributes (including the objectclass attribute) in a raw,
097      // unprocessed form as provided in the request.  One or more of these
098      // attributes may be invalid.
099      private List<RawAttribute> rawAttributes;
100    
101      // The set of operational attributes for the entry to add.
102      private Map<AttributeType,List<Attribute>> operationalAttributes;
103    
104      // The set of user attributes for the entry to add.
105      private Map<AttributeType,List<Attribute>> userAttributes;
106    
107      // The set of objectclasses for the entry to add.
108      private Map<ObjectClass,String> objectClasses;
109    
110      // The change number that has been assigned to this operation.
111      private long changeNumber;
112    
113    
114      /**
115       * Creates a new add operation with the provided information.
116       *
117       * @param  clientConnection  The client connection with which this operation
118       *                           is associated.
119       * @param  operationID       The operation ID for this operation.
120       * @param  messageID         The message ID of the request with which this
121       *                           operation is associated.
122       * @param  requestControls   The set of controls included in the request.
123       * @param  rawEntryDN        The raw DN of the entry to add from the client
124       *                           request.  This may or may not be a valid DN.
125       * @param  rawAttributes     The raw set of attributes from the client
126       *                           request (including the objectclass attribute).
127       *                           This may contain invalid attributes.
128       */
129      public AddOperationBasis(ClientConnection clientConnection, long operationID,
130                          int messageID, List<Control> requestControls,
131                          ByteString rawEntryDN, List<RawAttribute> rawAttributes)
132      {
133        super(clientConnection, operationID, messageID, requestControls);
134    
135    
136        this.rawEntryDN    = rawEntryDN;
137        this.rawAttributes = rawAttributes;
138    
139        responseControls      = new ArrayList<Control>();
140        cancelRequest         = null;
141        entryDN               = null;
142        userAttributes        = null;
143        operationalAttributes = null;
144        objectClasses         = null;
145        proxiedAuthorizationDN = null;
146        changeNumber          = -1;
147      }
148    
149    
150    
151      /**
152       * Creates a new add operation with the provided information.
153       *
154       * @param  clientConnection       The client connection with which this
155       *                                operation is associated.
156       * @param  operationID            The operation ID for this operation.
157       * @param  messageID              The message ID of the request with which
158       *                                this operation is associated.
159       * @param  requestControls        The set of controls included in the request.
160       * @param  entryDN                The DN for the entry.
161       * @param  objectClasses          The set of objectclasses for the entry.
162       * @param  userAttributes         The set of user attributes for the entry.
163       * @param  operationalAttributes  The set of operational attributes for the
164       *                                entry.
165       */
166      public AddOperationBasis(ClientConnection clientConnection, long operationID,
167                          int messageID, List<Control> requestControls,
168                          DN entryDN, Map<ObjectClass,String> objectClasses,
169                          Map<AttributeType,List<Attribute>> userAttributes,
170                          Map<AttributeType,List<Attribute>> operationalAttributes)
171      {
172        super(clientConnection, operationID, messageID, requestControls);
173    
174    
175        this.entryDN               = entryDN;
176        this.objectClasses         = objectClasses;
177        this.userAttributes        = userAttributes;
178        this.operationalAttributes = operationalAttributes;
179    
180        rawEntryDN = new ASN1OctetString(entryDN.toString());
181    
182        rawAttributes = new ArrayList<RawAttribute>();
183    
184        ArrayList<ASN1OctetString> ocValues = new ArrayList<ASN1OctetString>();
185        for (String s : objectClasses.values())
186        {
187          ocValues.add(new ASN1OctetString(s));
188        }
189    
190        LDAPAttribute ocAttr = new LDAPAttribute(ATTR_OBJECTCLASS, ocValues);
191        rawAttributes.add(ocAttr);
192    
193        for (List<Attribute> attrList : userAttributes.values())
194        {
195          for (Attribute a : attrList)
196          {
197            rawAttributes.add(new LDAPAttribute(a));
198          }
199        }
200    
201        for (List<Attribute> attrList : operationalAttributes.values())
202        {
203          for (Attribute a : attrList)
204          {
205            rawAttributes.add(new LDAPAttribute(a));
206          }
207        }
208    
209        responseControls = new ArrayList<Control>();
210        proxiedAuthorizationDN = null;
211        cancelRequest    = null;
212        changeNumber     = -1;
213      }
214    
215    
216      /**
217       * {@inheritDoc}
218       */
219      public final ByteString getRawEntryDN()
220      {
221        return rawEntryDN;
222      }
223    
224      /**
225       * {@inheritDoc}
226       */
227      public final void setRawEntryDN(ByteString rawEntryDN)
228      {
229        this.rawEntryDN = rawEntryDN;
230    
231        entryDN = null;
232      }
233    
234    
235      /**
236       * {@inheritDoc}
237       */
238      public final DN getEntryDN()
239      {
240        try
241        {
242          if (entryDN == null)
243          {
244            entryDN = DN.decode(rawEntryDN);
245          }
246        }
247        catch (DirectoryException de)
248        {
249          if (debugEnabled())
250          {
251            TRACER.debugCaught(DebugLogLevel.ERROR, de);
252          }
253    
254          setResultCode(de.getResultCode());
255          appendErrorMessage(de.getMessageObject());
256          setMatchedDN(de.getMatchedDN());
257          setReferralURLs(de.getReferralURLs());
258        }
259        return entryDN;
260      }
261    
262    
263      /**
264       * {@inheritDoc}
265       */
266      public final List<RawAttribute> getRawAttributes()
267      {
268        return rawAttributes;
269      }
270    
271    
272      /**
273       * {@inheritDoc}
274       */
275      public final void addRawAttribute(RawAttribute rawAttribute)
276      {
277        rawAttributes.add(rawAttribute);
278    
279        objectClasses         = null;
280        userAttributes        = null;
281        operationalAttributes = null;
282      }
283    
284    
285      /**
286       * {@inheritDoc}
287       */
288      public final void setRawAttributes(List<RawAttribute> rawAttributes)
289      {
290        this.rawAttributes = rawAttributes;
291    
292        objectClasses         = null;
293        userAttributes        = null;
294        operationalAttributes = null;
295      }
296    
297    
298    
299      /**
300       * {@inheritDoc}
301       */
302      public final Map<ObjectClass,String> getObjectClasses()
303      {
304        if (objectClasses == null){
305          computeObjectClassesAndAttributes();
306        }
307        return objectClasses;
308      }
309    
310    
311    
312      /**
313       * {@inheritDoc}
314       */
315      public final void addObjectClass(ObjectClass objectClass, String name)
316      {
317        objectClasses.put(objectClass, name);
318      }
319    
320    
321    
322      /**
323       * {@inheritDoc}
324       */
325      public final void removeObjectClass(ObjectClass objectClass)
326      {
327        objectClasses.remove(objectClass);
328      }
329    
330    
331    
332      /**
333       * {@inheritDoc}
334       */
335      public final Map<AttributeType,List<Attribute>> getUserAttributes()
336      {
337        if (userAttributes == null){
338          computeObjectClassesAndAttributes();
339        }
340        return userAttributes;
341      }
342    
343    
344      /**
345       * {@inheritDoc}
346       */
347      public final Map<AttributeType,List<Attribute>> getOperationalAttributes()
348      {
349        if (operationalAttributes == null){
350          computeObjectClassesAndAttributes();
351        }
352        return operationalAttributes;
353      }
354    
355      /**
356       * Build the objectclasses, the user attributes and the operational attributes
357       * if there are not already computed.
358       */
359      private final void computeObjectClassesAndAttributes()
360      {
361        if ((objectClasses == null) || (userAttributes == null) ||
362            (operationalAttributes == null))
363        {
364          objectClasses         = new HashMap<ObjectClass,String>();
365          userAttributes        = new HashMap<AttributeType,List<Attribute>>();
366          operationalAttributes = new HashMap<AttributeType,List<Attribute>>();
367    
368          for (RawAttribute a : rawAttributes)
369          {
370            try
371            {
372              Attribute attr = a.toAttribute();
373              AttributeType attrType = attr.getAttributeType();
374    
375              // If the attribute type is marked "NO-USER-MODIFICATION" then fail
376              // unless this is an internal operation or is related to
377              // synchronization in some way.
378              if (attrType.isNoUserModification())
379              {
380                if (! (isInternalOperation() || isSynchronizationOperation()))
381                {
382                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
383                  appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
384                          String.valueOf(entryDN),
385                          attr.getName()));
386    
387                  objectClasses = null;
388                  userAttributes = null;
389                  operationalAttributes = null;
390                  return;
391                }
392              }
393    
394              if (attrType.isObjectClassType())
395              {
396                for (ByteString os : a.getValues())
397                {
398                  String ocName = os.toString();
399                  ObjectClass oc =
400                    DirectoryServer.getObjectClass(toLowerCase(ocName));
401                  if (oc == null)
402                  {
403                    oc = DirectoryServer.getDefaultObjectClass(ocName);
404                  }
405    
406                  objectClasses.put(oc,ocName);
407                }
408              }
409              else if (attrType.isOperational())
410              {
411                List<Attribute> attrs = operationalAttributes.get(attrType);
412                if (attrs == null)
413                {
414                  attrs = new ArrayList<Attribute>(1);
415                  attrs.add(attr);
416                  operationalAttributes.put(attrType, attrs);
417                }
418                else
419                {
420                  attrs.add(attr);
421                }
422              }
423              else
424              {
425                List<Attribute> attrs = userAttributes.get(attrType);
426                if (attrs == null)
427                {
428                  attrs = new ArrayList<Attribute>(1);
429                  attrs.add(attr);
430                  userAttributes.put(attrType, attrs);
431                }
432                else
433                {
434                  // Check to see if any of the existing attributes in the list
435                  // have the same set of options.  If so, then add the values
436                  // to that attribute.
437                  boolean attributeSeen = false;
438                  for (Attribute ea : attrs)
439                  {
440                    if (ea.optionsEqual(attr.getOptions()))
441                    {
442                      LinkedHashSet<AttributeValue> valueSet = ea.getValues();
443                      valueSet.addAll(attr.getValues());
444                      attributeSeen = true;
445                    }
446                  }
447                  if (!attributeSeen)
448                  {
449                    // This is the first occurrence of the attribute and options.
450                    attrs.add(attr);
451                  }
452                }
453              }
454            }
455            catch (LDAPException le)
456            {
457              setResultCode(ResultCode.valueOf(le.getResultCode()));
458              appendErrorMessage(le.getMessageObject());
459    
460              objectClasses = null;
461              userAttributes = null;
462              operationalAttributes = null;
463            }
464          }
465        }
466      }
467    
468      /**
469       * {@inheritDoc}
470       */
471      public final void setAttribute(AttributeType attributeType,
472                                     List<Attribute> attributeList)
473      {
474        if (attributeType.isOperational())
475        {
476          if ((attributeList == null) || (attributeList.isEmpty()))
477          {
478            operationalAttributes.remove(attributeType);
479          }
480          else
481          {
482            operationalAttributes.put(attributeType, attributeList);
483          }
484        }
485        else
486        {
487          if ((attributeList == null) || (attributeList.isEmpty()))
488          {
489            userAttributes.remove(attributeType);
490          }
491          else
492          {
493            userAttributes.put(attributeType, attributeList);
494          }
495        }
496      }
497    
498    
499      /**
500       * {@inheritDoc}
501       */
502      public final void removeAttribute(AttributeType attributeType)
503      {
504        if (attributeType.isOperational())
505        {
506          operationalAttributes.remove(attributeType);
507        }
508        else
509        {
510          userAttributes.remove(attributeType);
511        }
512      }
513    
514      /**
515       * {@inheritDoc}
516       */
517      public final long getChangeNumber()
518      {
519        return changeNumber;
520      }
521    
522    
523      /**
524       * {@inheritDoc}
525       */
526      public final void setChangeNumber(long changeNumber)
527      {
528        this.changeNumber = changeNumber;
529      }
530    
531    
532      /**
533       * {@inheritDoc}
534       */
535      public final OperationType getOperationType()
536      {
537        // Note that no debugging will be done in this method because it is a likely
538        // candidate for being called by the logging subsystem.
539    
540        return OperationType.ADD;
541      }
542    
543    
544      /**
545       * {@inheritDoc}
546       */
547      public final String[][] getRequestLogElements()
548      {
549        // Note that no debugging will be done in this method because it is a likely
550        // candidate for being called by the logging subsystem.
551    
552        return new String[][]
553        {
554          new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
555        };
556      }
557    
558    
559    
560      /**
561       * {@inheritDoc}
562       */
563      public final String[][] getResponseLogElements()
564      {
565        // Note that no debugging will be done in this method because it is a likely
566        // candidate for being called by the logging subsystem.
567    
568        String resultCode = String.valueOf(getResultCode().getIntValue());
569    
570        String errorMessage;
571        MessageBuilder errorMessageBuffer = getErrorMessage();
572        if (errorMessageBuffer == null)
573        {
574          errorMessage = null;
575        }
576        else
577        {
578          errorMessage = errorMessageBuffer.toString();
579        }
580    
581        String matchedDNStr;
582        DN matchedDN = getMatchedDN();
583        if (matchedDN == null)
584        {
585          matchedDNStr = null;
586        }
587        else
588        {
589          matchedDNStr = matchedDN.toString();
590        }
591    
592        String referrals;
593        List<String> referralURLs = getReferralURLs();
594        if ((referralURLs == null) || referralURLs.isEmpty())
595        {
596          referrals = null;
597        }
598        else
599        {
600          StringBuilder buffer = new StringBuilder();
601          Iterator<String> iterator = referralURLs.iterator();
602          buffer.append(iterator.next());
603    
604          while (iterator.hasNext())
605          {
606            buffer.append(", ");
607            buffer.append(iterator.next());
608          }
609    
610          referrals = buffer.toString();
611        }
612    
613        String processingTime =
614             String.valueOf(getProcessingTime());
615    
616        return new String[][]
617        {
618          new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
619          new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
620          new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
621          new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
622          new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
623        };
624      }
625    
626      /**
627       * {@inheritDoc}
628       */
629      public DN getProxiedAuthorizationDN()
630      {
631        return proxiedAuthorizationDN;
632      }
633    
634      /**
635       * {@inheritDoc}
636       */
637      public final ArrayList<Control> getResponseControls()
638      {
639        return responseControls;
640      }
641    
642    
643    
644      /**
645       * {@inheritDoc}
646       */
647      public final void addResponseControl(Control control)
648      {
649        responseControls.add(control);
650      }
651    
652    
653    
654      /**
655       * {@inheritDoc}
656       */
657      public final void removeResponseControl(Control control)
658      {
659        responseControls.remove(control);
660      }
661    
662    
663    
664      /**
665       * {@inheritDoc}
666       */
667      public final void toString(StringBuilder buffer)
668      {
669        buffer.append("AddOperation(connID=");
670        buffer.append(clientConnection.getConnectionID());
671        buffer.append(", opID=");
672        buffer.append(operationID);
673        buffer.append(", dn=");
674        buffer.append(rawEntryDN);
675        buffer.append(")");
676      }
677    
678      /**
679       * {@inheritDoc}
680       */
681      public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
682      {
683        this.proxiedAuthorizationDN = proxiedAuthorizationDN;
684      }
685    
686      /**
687       * {@inheritDoc}
688       */
689      public final void run()
690      {
691        setResultCode(ResultCode.UNDEFINED);
692    
693        // Start the processing timer.
694        setProcessingStartTime();
695    
696        // Log the add request message.
697        logAddRequest(this);
698    
699        // Get the plugin config manager that will be used for invoking plugins.
700        PluginConfigManager pluginConfigManager =
701          DirectoryServer.getPluginConfigManager();
702    
703        // This flag is set to true as soon as a workflow has been executed.
704        boolean workflowExecuted = false;
705    
706        try
707        {
708          // Check for and handle a request to cancel this operation.
709          checkIfCanceled(false);
710    
711          // Invoke the pre-parse add plugins.
712          PluginResult.PreParse preParseResult =
713            pluginConfigManager.invokePreParseAddPlugins(this);
714    
715          if(!preParseResult.continueProcessing())
716          {
717            setResultCode(preParseResult.getResultCode());
718            appendErrorMessage(preParseResult.getErrorMessage());
719            setMatchedDN(preParseResult.getMatchedDN());
720            setReferralURLs(preParseResult.getReferralURLs());
721            return;
722          }
723    
724          // Check for and handle a request to cancel this operation.
725          checkIfCanceled(false);
726    
727          // Process the entry DN and set of attributes to convert them from their
728          // raw forms as provided by the client to the forms required for the rest
729          // of the add processing.
730          DN entryDN = getEntryDN();
731          if (entryDN == null){
732            return;
733          }
734    
735          // Retrieve the network group attached to the client connection
736          // and get a workflow to process the operation.
737          NetworkGroup ng = getClientConnection().getNetworkGroup();
738          Workflow workflow = ng.getWorkflowCandidate(entryDN);
739          if (workflow == null)
740          {
741            // We have found no workflow for the requested base DN, just return
742            // a no such entry result code and stop the processing.
743            updateOperationErrMsgAndResCode();
744            return;
745          }
746          workflow.execute(this);
747          workflowExecuted = true;
748    
749        }
750        catch(CanceledOperationException coe)
751        {
752          if (debugEnabled())
753          {
754            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
755          }
756    
757          setResultCode(ResultCode.CANCELED);
758          cancelResult = new CancelResult(ResultCode.CANCELED, null);
759    
760          appendErrorMessage(coe.getCancelRequest().getCancelReason());
761        }
762        finally
763        {
764          // Stop the processing timer.
765          setProcessingStopTime();
766    
767          if(cancelRequest == null || cancelResult == null ||
768              cancelResult.getResultCode() != ResultCode.CANCELED ||
769              cancelRequest.notifyOriginalRequestor() ||
770              DirectoryServer.notifyAbandonedOperations())
771          {
772            clientConnection.sendResponse(this);
773          }
774    
775    
776          // Log the add response message.
777          logAddResponse(this);
778    
779          // Notifies any persistent searches that might be registered with the
780          // server.
781          notifyPersistentSearches(workflowExecuted);
782    
783          // Invoke the post-response add plugins.
784          invokePostResponsePlugins(workflowExecuted);
785    
786          // If no cancel result, set it
787          if(cancelResult == null)
788          {
789            cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
790          }
791        }
792      }
793    
794    
795      /**
796       * Invokes the post response plugins. If a workflow has been executed
797       * then invoke the post response plugins provided by the workflow
798       * elements of the worklfow, otherwise invoke the post reponse plugins
799       * that have been registered with the current operation.
800       *
801       * @param workflowExecuted <code>true</code> if a workflow has been
802       *                         executed
803       */
804      private void invokePostResponsePlugins(boolean workflowExecuted)
805      {
806        // Get the plugin config manager that will be used for invoking plugins.
807        PluginConfigManager pluginConfigManager =
808          DirectoryServer.getPluginConfigManager();
809    
810        // Invoke the post response plugins
811        if (workflowExecuted)
812        {
813          // Invoke the post response plugins that have been registered by
814          // the workflow elements
815          List localOperations =
816            (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
817    
818          if (localOperations != null)
819          {
820            for (Object localOp : localOperations)
821            {
822              LocalBackendAddOperation localOperation =
823                (LocalBackendAddOperation)localOp;
824              pluginConfigManager.invokePostResponseAddPlugins(localOperation);
825            }
826          }
827        }
828        else
829        {
830          // Invoke the post response plugins that have been registered with
831          // the current operation
832          pluginConfigManager.invokePostResponseAddPlugins(this);
833        }
834      }
835    
836    
837      /**
838       * Notifies any persistent searches that might be registered with the server.
839       * If no workflow has been executed then don't notify persistent searches.
840       *
841       * @param workflowExecuted <code>true</code> if a workflow has been
842       *                         executed
843       */
844      private void notifyPersistentSearches(boolean workflowExecuted)
845      {
846        if (! workflowExecuted)
847        {
848          return;
849        }
850    
851        List localOperations =
852          (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
853    
854        if (localOperations != null)
855        {
856          for (Object localOp : localOperations)
857          {
858            LocalBackendAddOperation localOperation =
859              (LocalBackendAddOperation)localOp;
860    
861            if ((getResultCode() == ResultCode.SUCCESS) &&
862                (localOperation.getEntryToAdd() != null))
863            {
864              for (PersistentSearch persistentSearch :
865                DirectoryServer.getPersistentSearches())
866              {
867                try
868                {
869                  persistentSearch.processAdd(localOperation,
870                      localOperation.getEntryToAdd());
871                }
872                catch (Exception e)
873                {
874                  if (debugEnabled())
875                  {
876                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
877                  }
878    
879                  Message message = ERR_ADD_ERROR_NOTIFYING_PERSISTENT_SEARCH.get(
880                      String.valueOf(persistentSearch), getExceptionMessage(e));
881                  logError(message);
882    
883                  DirectoryServer.deregisterPersistentSearch(persistentSearch);
884                }
885              }
886            }
887          }
888        }
889      }
890    
891    
892      /**
893       * Updates the error message and the result code of the operation.
894       *
895       * This method is called because no workflows were found to process
896       * the operation.
897       */
898      private void updateOperationErrMsgAndResCode()
899      {
900        DN entryDN = getEntryDN();
901        DN parentDN = entryDN.getParentDNInSuffix();
902        if (parentDN == null)
903        {
904          // Either this entry is a suffix or doesn't belong in the directory.
905          if (DirectoryServer.isNamingContext(entryDN))
906          {
907            // This is fine.  This entry is one of the configured suffixes.
908            return;
909          }
910          if (entryDN.isNullDN())
911          {
912            // This is not fine.  The root DSE cannot be added.
913            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
914            appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
915            return;
916          }
917          // The entry doesn't have a parent but isn't a suffix.  This is not
918          // allowed.
919          setResultCode(ResultCode.NO_SUCH_OBJECT);
920          appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(
921            String.valueOf(entryDN)));
922          return;
923        }
924        // The suffix does not exist
925        setResultCode(ResultCode.NO_SUCH_OBJECT);
926        appendErrorMessage(ERR_ADD_ENTRY_UNKNOWN_SUFFIX.get(
927          String.valueOf(entryDN)));
928      }
929    
930    
931      /**
932       * {@inheritDoc}
933       *
934       * This method always returns null.
935       */
936      public Entry getEntryToAdd()
937      {
938        return null;
939      }
940    
941    }
942