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 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.workflowelement.localbackend;
028    
029    
030    
031    import java.util.ArrayList;
032    import java.util.Iterator;
033    import java.util.LinkedHashSet;
034    import java.util.LinkedList;
035    import java.util.List;
036    import java.util.concurrent.locks.Lock;
037    
038    import org.opends.messages.Message;
039    import org.opends.messages.MessageBuilder;
040    import org.opends.server.api.Backend;
041    import org.opends.server.api.ChangeNotificationListener;
042    import org.opends.server.api.ClientConnection;
043    import org.opends.server.api.SynchronizationProvider;
044    import org.opends.server.api.plugin.PluginResult;
045    import org.opends.server.controls.LDAPAssertionRequestControl;
046    import org.opends.server.controls.LDAPPostReadRequestControl;
047    import org.opends.server.controls.LDAPPostReadResponseControl;
048    import org.opends.server.controls.LDAPPreReadRequestControl;
049    import org.opends.server.controls.LDAPPreReadResponseControl;
050    import org.opends.server.controls.ProxiedAuthV1Control;
051    import org.opends.server.controls.ProxiedAuthV2Control;
052    import org.opends.server.core.AccessControlConfigManager;
053    import org.opends.server.core.DirectoryServer;
054    import org.opends.server.core.ModifyDNOperation;
055    import org.opends.server.core.ModifyDNOperationWrapper;
056    import org.opends.server.core.PluginConfigManager;
057    import org.opends.server.loggers.debug.DebugTracer;
058    import org.opends.server.protocols.asn1.ASN1OctetString;
059    import org.opends.server.types.Attribute;
060    import org.opends.server.types.AttributeType;
061    import org.opends.server.types.AttributeValue;
062    import org.opends.server.types.ByteString;
063    import org.opends.server.types.CanceledOperationException;
064    import org.opends.server.types.Control;
065    import org.opends.server.types.DebugLogLevel;
066    import org.opends.server.types.DirectoryException;
067    import org.opends.server.types.DN;
068    import org.opends.server.types.Entry;
069    import org.opends.server.types.LDAPException;
070    import org.opends.server.types.LockManager;
071    import org.opends.server.types.Modification;
072    import org.opends.server.types.ModificationType;
073    import org.opends.server.types.Privilege;
074    import org.opends.server.types.RDN;
075    import org.opends.server.types.ResultCode;
076    import org.opends.server.types.SearchFilter;
077    import org.opends.server.types.SearchResultEntry;
078    import org.opends.server.types.SynchronizationProviderResult;
079    import org.opends.server.types.operation.PostOperationModifyDNOperation;
080    import org.opends.server.types.operation.PostResponseModifyDNOperation;
081    import org.opends.server.types.operation.PreOperationModifyDNOperation;
082    import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
083    
084    import static org.opends.messages.CoreMessages.*;
085    import static org.opends.server.loggers.ErrorLogger.*;
086    import static org.opends.server.loggers.debug.DebugLogger.*;
087    import static org.opends.server.util.ServerConstants.*;
088    import static org.opends.server.util.StaticUtils.*;
089    
090    
091    
092    /**
093     * This class defines an operation used to move an entry in a local backend
094     * of the Directory Server.
095     */
096    public class LocalBackendModifyDNOperation
097      extends ModifyDNOperationWrapper
098      implements PreOperationModifyDNOperation,
099                 PostOperationModifyDNOperation,
100                 PostResponseModifyDNOperation,
101                 PostSynchronizationModifyDNOperation
102    {
103      /**
104       * The tracer object for the debug logger.
105       */
106      private static final DebugTracer TRACER = getTracer();
107    
108    
109    
110      // The backend in which the operation is to be processed.
111      private Backend backend;
112    
113      // Indicates whether the no-op control was included in the request.
114      private boolean noOp;
115    
116      // The client connection on which this operation was requested.
117      private ClientConnection clientConnection;
118    
119      // The original DN of the entry.
120      DN entryDN;
121    
122      // The current entry, before it is renamed.
123      private Entry currentEntry;
124    
125      // The new entry, as it will appear after it has been renamed.
126      private Entry newEntry;
127    
128      // The LDAP post-read request control, if present in the request.
129      private LDAPPostReadRequestControl postReadRequest;
130    
131      // The LDAP pre-read request control, if present in the request.
132      private LDAPPreReadRequestControl preReadRequest;
133    
134      // The new RDN for the entry.
135      private RDN newRDN;
136    
137    
138    
139      /**
140       * Creates a new operation that may be used to move an entry in a
141       * local backend of the Directory Server.
142       *
143       * @param operation The operation to enhance.
144       */
145      public LocalBackendModifyDNOperation (ModifyDNOperation operation)
146      {
147        super(operation);
148        LocalBackendWorkflowElement.attachLocalOperation (operation, this);
149      }
150    
151    
152    
153      /**
154       * Retrieves the current entry, before it is renamed.  This will not be
155       * available to pre-parse plugins or during the conflict resolution portion of
156       * the synchronization processing.
157       *
158       * @return  The current entry, or <CODE>null</CODE> if it is not yet
159       *           available.
160       */
161      public final Entry getOriginalEntry()
162      {
163        return currentEntry;
164      }
165    
166    
167    
168      /**
169       * Retrieves the new entry, as it will appear after it is renamed.  This will
170       * not be  available to pre-parse plugins or during the conflict resolution
171       * portion of the synchronization processing.
172       *
173       * @return  The updated entry, or <CODE>null</CODE> if it is not yet
174       *           available.
175       */
176      public final Entry getUpdatedEntry()
177      {
178        return newEntry;
179      }
180    
181    
182    
183      /**
184       * Process this modify DN operation in a local backend.
185       *
186       * @param  backend  The backend in which the modify DN operation should be
187       *                  processed.
188       *
189       * @throws CanceledOperationException if this operation should be
190       * cancelled
191       */
192      void processLocalModifyDN(Backend backend) throws CanceledOperationException {
193        boolean executePostOpPlugins = false;
194    
195        this.backend = backend;
196    
197        clientConnection = getClientConnection();
198    
199        // Get the plugin config manager that will be used for invoking plugins.
200        PluginConfigManager pluginConfigManager =
201             DirectoryServer.getPluginConfigManager();
202    
203        // Check for a request to cancel this operation.
204        checkIfCanceled(false);
205    
206        // Create a labeled block of code that we can break out of if a problem is
207        // detected.
208    modifyDNProcessing:
209        {
210          // Process the entry DN, newRDN, and newSuperior elements from their raw
211          // forms as provided by the client to the forms required for the rest of
212          // the modify DN processing.
213          entryDN = getEntryDN();
214    
215          newRDN = getNewRDN();
216          if (newRDN == null)
217          {
218            break modifyDNProcessing;
219          }
220    
221          DN newSuperior = getNewSuperior();
222          if ((newSuperior == null) &&
223              (getRawNewSuperior() != null))
224          {
225            break modifyDNProcessing;
226          }
227    
228          // Construct the new DN to use for the entry.
229          DN parentDN;
230          if (newSuperior == null)
231          {
232            parentDN = entryDN.getParentDNInSuffix();
233          }
234          else
235          {
236            parentDN = newSuperior;
237          }
238    
239          if ((parentDN == null) || parentDN.isNullDN())
240          {
241            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
242            appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
243            break modifyDNProcessing;
244          }
245    
246          DN newDN = parentDN.concat(newRDN);
247    
248          // Get the backend for the current entry, and the backend for the new
249          // entry.  If either is null, or if they are different, then fail.
250          Backend currentBackend = backend;
251          if (currentBackend == null)
252          {
253            setResultCode(ResultCode.NO_SUCH_OBJECT);
254            appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
255                                    String.valueOf(entryDN)));
256            break modifyDNProcessing;
257          }
258    
259          Backend newBackend = DirectoryServer.getBackend(newDN);
260          if (newBackend == null)
261          {
262            setResultCode(ResultCode.NO_SUCH_OBJECT);
263            appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
264                                    String.valueOf(entryDN),
265                                    String.valueOf(newDN)));
266            break modifyDNProcessing;
267          }
268          else if (! currentBackend.equals(newBackend))
269          {
270            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
271            appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
272                                    String.valueOf(entryDN),
273                                    String.valueOf(newDN)));
274            break modifyDNProcessing;
275          }
276    
277    
278          // Check for a request to cancel this operation.
279          checkIfCanceled(false);
280    
281    
282          // Acquire write locks for the current and new DN.
283          Lock currentLock = null;
284          for (int i=0; i < 3; i++)
285          {
286            currentLock = LockManager.lockWrite(entryDN);
287            if (currentLock != null)
288            {
289              break;
290            }
291          }
292    
293          if (currentLock == null)
294          {
295            setResultCode(DirectoryServer.getServerErrorResultCode());
296            appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(
297                                    String.valueOf(entryDN)));
298            break modifyDNProcessing;
299          }
300    
301          Lock newLock = null;
302          try
303          {
304            for (int i=0; i < 3; i++)
305            {
306              newLock = LockManager.lockWrite(newDN);
307              if (newLock != null)
308              {
309                break;
310              }
311            }
312          }
313          catch (Exception e)
314          {
315            if (debugEnabled())
316            {
317              TRACER.debugCaught(DebugLogLevel.ERROR, e);
318            }
319    
320            LockManager.unlock(entryDN, currentLock);
321    
322            if (newLock != null)
323            {
324              LockManager.unlock(newDN, newLock);
325            }
326    
327            setResultCode(DirectoryServer.getServerErrorResultCode());
328            appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(
329                                    String.valueOf(entryDN), String.valueOf(newDN),
330                                    getExceptionMessage(e)));
331            break modifyDNProcessing;
332          }
333    
334          if (newLock == null)
335          {
336            LockManager.unlock(entryDN, currentLock);
337    
338            setResultCode(DirectoryServer.getServerErrorResultCode());
339            appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(
340                                    String.valueOf(entryDN),
341                                    String.valueOf(newDN)));
342            break modifyDNProcessing;
343          }
344    
345          try
346          {
347            // Check for a request to cancel this operation.
348            checkIfCanceled(false);
349    
350    
351            // Get the current entry from the appropriate backend.  If it doesn't
352            // exist, then fail.
353            try
354            {
355              currentEntry = currentBackend.getEntry(entryDN);
356            }
357            catch (DirectoryException de)
358            {
359              if (debugEnabled())
360              {
361                TRACER.debugCaught(DebugLogLevel.ERROR, de);
362              }
363    
364              setResponseData(de);
365              break modifyDNProcessing;
366            }
367    
368            if (getOriginalEntry() == null)
369            {
370              // See if one of the entry's ancestors exists.
371              parentDN = entryDN.getParentDNInSuffix();
372              while (parentDN != null)
373              {
374                try
375                {
376                  if (DirectoryServer.entryExists(parentDN))
377                  {
378                    setMatchedDN(parentDN);
379                    break;
380                  }
381                }
382                catch (Exception e)
383                {
384                  if (debugEnabled())
385                  {
386                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
387                  }
388                  break;
389                }
390    
391                parentDN = parentDN.getParentDNInSuffix();
392              }
393    
394              setResultCode(ResultCode.NO_SUCH_OBJECT);
395              appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
396                                      String.valueOf(entryDN)));
397              break modifyDNProcessing;
398            }
399    
400    
401            // Invoke any conflict resolution processing that might be needed by the
402            // synchronization provider.
403            for (SynchronizationProvider provider :
404                 DirectoryServer.getSynchronizationProviders())
405            {
406              try
407              {
408                SynchronizationProviderResult result =
409                     provider.handleConflictResolution(this);
410                if (! result.continueProcessing())
411                {
412                  setResultCode(result.getResultCode());
413                  appendErrorMessage(result.getErrorMessage());
414                  setMatchedDN(result.getMatchedDN());
415                  setReferralURLs(result.getReferralURLs());
416                  break modifyDNProcessing;
417                }
418              }
419              catch (DirectoryException de)
420              {
421                if (debugEnabled())
422                {
423                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
424                }
425    
426                logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
427                              getConnectionID(), getOperationID(),
428                              getExceptionMessage(de)));
429    
430                setResponseData(de);
431                break modifyDNProcessing;
432              }
433            }
434    
435    
436            // Check to see if there are any controls in the request.  If so, then
437            // see if there is any special processing required.
438            try
439            {
440              handleRequestControls();
441            }
442            catch (DirectoryException de)
443            {
444              if (debugEnabled())
445              {
446                TRACER.debugCaught(DebugLogLevel.ERROR, de);
447              }
448    
449              setResponseData(de);
450              break modifyDNProcessing;
451            }
452    
453    
454            // Check to see if the client has permission to perform the
455            // modify DN.
456    
457            // FIXME: for now assume that this will check all permission
458            // pertinent to the operation. This includes proxy authorization
459            // and any other controls specified.
460    
461            // FIXME: earlier checks to see if the entry or new superior
462            // already exists may have already exposed sensitive information
463            // to the client.
464            if (! AccessControlConfigManager.getInstance().
465                       getAccessControlHandler().isAllowed(this))
466            {
467              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
468              appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
469                                      String.valueOf(entryDN)));
470              break modifyDNProcessing;
471            }
472    
473            // Duplicate the entry and set its new DN.  Also, create an empty list
474            // to hold the attribute-level modifications.
475            newEntry = currentEntry.duplicate(false);
476            newEntry.setDN(newDN);
477    
478            // init the modifications
479            addModification(null);
480            List<Modification> modifications = this.getModifications();
481    
482    
483    
484            // Apply any changes to the entry based on the change in its RDN.  Also,
485            // perform schema checking on the updated entry.
486            try
487            {
488              applyRDNChanges(modifications);
489            }
490            catch (DirectoryException de)
491            {
492              if (debugEnabled())
493              {
494                TRACER.debugCaught(DebugLogLevel.ERROR, de);
495              }
496    
497              setResponseData(de);
498              break modifyDNProcessing;
499            }
500    
501    
502            // Check for a request to cancel this operation.
503            checkIfCanceled(false);
504    
505            // Get a count of the current number of modifications.  The
506            // pre-operation plugins may alter this list, and we need to be able to
507            // identify which changes were made after they're done.
508            int modCount = modifications.size();
509    
510    
511            // If the operation is not a synchronization operation,
512            // Invoke the pre-operation modify DN plugins.
513            if (! isSynchronizationOperation())
514            {
515              executePostOpPlugins = true;
516              PluginResult.PreOperation preOpResult =
517                  pluginConfigManager.invokePreOperationModifyDNPlugins(this);
518              if (!preOpResult.continueProcessing())
519              {
520                setResultCode(preOpResult.getResultCode());
521                appendErrorMessage(preOpResult.getErrorMessage());
522                setMatchedDN(preOpResult.getMatchedDN());
523                setReferralURLs(preOpResult.getReferralURLs());
524                break modifyDNProcessing;
525              }
526            }
527    
528    
529            // Check to see if any of the pre-operation plugins made any changes to
530            // the entry.  If so, then apply them.
531            if (modifications.size() > modCount)
532            {
533              try
534              {
535                applyPreOpModifications(modifications, modCount);
536              }
537              catch (DirectoryException de)
538              {
539                if (debugEnabled())
540                {
541                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
542                }
543    
544                setResponseData(de);
545                break modifyDNProcessing;
546              }
547            }
548    
549    
550            // Check for a request to cancel this operation.
551            checkIfCanceled(true);
552    
553            // Actually perform the modify DN operation.
554            // This should include taking
555            // care of any synchronization that might be needed.
556            try
557            {
558              // If it is not a private backend, then check to see if the server or
559              // backend is operating in read-only mode.
560              if (! currentBackend.isPrivateBackend())
561              {
562                switch (DirectoryServer.getWritabilityMode())
563                {
564                  case DISABLED:
565                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
566                    appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
567                                            String.valueOf(entryDN)));
568                    break modifyDNProcessing;
569    
570                  case INTERNAL_ONLY:
571                    if (! (isInternalOperation() || isSynchronizationOperation()))
572                    {
573                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
574                      appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
575                                              String.valueOf(entryDN)));
576                      break modifyDNProcessing;
577                    }
578                }
579    
580                switch (currentBackend.getWritabilityMode())
581                {
582                  case DISABLED:
583                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
584                    appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
585                                            String.valueOf(entryDN)));
586                    break modifyDNProcessing;
587    
588                  case INTERNAL_ONLY:
589                    if (! (isInternalOperation() || isSynchronizationOperation()))
590                    {
591                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
592                      appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
593                                              String.valueOf(entryDN)));
594                      break modifyDNProcessing;
595                    }
596                }
597              }
598    
599    
600              if (noOp)
601              {
602                appendErrorMessage(INFO_MODDN_NOOP.get());
603                setResultCode(ResultCode.NO_OPERATION);
604              }
605              else
606              {
607                for (SynchronizationProvider provider :
608                     DirectoryServer.getSynchronizationProviders())
609                {
610                  try
611                  {
612                    SynchronizationProviderResult result =
613                        provider.doPreOperation(this);
614                    if (! result.continueProcessing())
615                    {
616                      setResultCode(result.getResultCode());
617                      appendErrorMessage(result.getErrorMessage());
618                      setMatchedDN(result.getMatchedDN());
619                      setReferralURLs(result.getReferralURLs());
620                      break modifyDNProcessing;
621                    }
622                  }
623                  catch (DirectoryException de)
624                  {
625                    if (debugEnabled())
626                    {
627                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
628                    }
629    
630                    logError(ERR_MODDN_SYNCH_PREOP_FAILED.get(getConnectionID(),
631                                  getOperationID(), getExceptionMessage(de)));
632                    setResponseData(de);
633                    break modifyDNProcessing;
634                  }
635                }
636    
637                currentBackend.renameEntry(entryDN, newEntry, this);
638              }
639    
640    
641              // Attach the pre-read and/or post-read controls to the response if
642              // appropriate.
643              processReadEntryControls();
644    
645    
646              if (! noOp)
647              {
648                setResultCode(ResultCode.SUCCESS);
649              }
650            }
651            catch (DirectoryException de)
652            {
653              if (debugEnabled())
654              {
655                TRACER.debugCaught(DebugLogLevel.ERROR, de);
656              }
657    
658              setResponseData(de);
659              break modifyDNProcessing;
660            }
661          }
662          finally
663          {
664            LockManager.unlock(entryDN, currentLock);
665            LockManager.unlock(newDN, newLock);
666          }
667        }
668    
669        for (SynchronizationProvider provider :
670            DirectoryServer.getSynchronizationProviders())
671        {
672          try
673          {
674            provider.doPostOperation(this);
675          }
676          catch (DirectoryException de)
677          {
678            if (debugEnabled())
679            {
680              TRACER.debugCaught(DebugLogLevel.ERROR, de);
681            }
682    
683            logError(ERR_MODDN_SYNCH_POSTOP_FAILED.get(getConnectionID(),
684                getOperationID(), getExceptionMessage(de)));
685            setResponseData(de);
686            break;
687          }
688        }
689    
690        // Invoke the post-operation or post-synchronization modify DN plugins.
691        if (isSynchronizationOperation())
692        {
693          if (getResultCode() == ResultCode.SUCCESS)
694          {
695            pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
696          }
697        }
698        else if (executePostOpPlugins)
699        {
700          PluginResult.PostOperation postOpResult =
701               pluginConfigManager.invokePostOperationModifyDNPlugins(this);
702          if (!postOpResult.continueProcessing())
703          {
704            setResultCode(postOpResult.getResultCode());
705            appendErrorMessage(postOpResult.getErrorMessage());
706            setMatchedDN(postOpResult.getMatchedDN());
707            setReferralURLs(postOpResult.getReferralURLs());
708            return;
709          }
710        }
711    
712    
713        // Notify any change notification listeners that might be registered with
714        // the server.
715        if (getResultCode() == ResultCode.SUCCESS)
716        {
717          for (ChangeNotificationListener changeListener :
718               DirectoryServer.getChangeNotificationListeners())
719          {
720            try
721            {
722              changeListener.handleModifyDNOperation(this, currentEntry, newEntry);
723            }
724            catch (Exception e)
725            {
726              if (debugEnabled())
727              {
728                TRACER.debugCaught(DebugLogLevel.ERROR, e);
729              }
730    
731              Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
732                  getExceptionMessage(e));
733              logError(message);
734            }
735          }
736        }
737      }
738    
739    
740    
741      /**
742       * Processes the set of controls included in the request.
743       *
744       * @throws  DirectoryException  If a problem occurs that should cause the
745       *                              modify DN operation to fail.
746       */
747      private void handleRequestControls()
748              throws DirectoryException
749      {
750        List<Control> requestControls = getRequestControls();
751        if ((requestControls != null) && (! requestControls.isEmpty()))
752        {
753          for (int i=0; i < requestControls.size(); i++)
754          {
755            Control c   = requestControls.get(i);
756            String  oid = c.getOID();
757    
758            if (! AccessControlConfigManager.getInstance().
759                       getAccessControlHandler().isAllowed(entryDN,  this, c))
760            {
761              throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
762                             ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
763            }
764    
765            if (oid.equals(OID_LDAP_ASSERTION))
766            {
767              LDAPAssertionRequestControl assertControl;
768              if (c instanceof LDAPAssertionRequestControl)
769              {
770                assertControl = (LDAPAssertionRequestControl) c;
771              }
772              else
773              {
774                try
775                {
776                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
777                  requestControls.set(i, assertControl);
778                }
779                catch (LDAPException le)
780                {
781                  if (debugEnabled())
782                  {
783                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
784                  }
785    
786                  throw new DirectoryException(
787                                 ResultCode.valueOf(le.getResultCode()),
788                                 le.getMessageObject());
789                }
790              }
791    
792              try
793              {
794                // FIXME -- We need to determine whether the current user has
795                //          permission to make this determination.
796                SearchFilter filter = assertControl.getSearchFilter();
797                if (! filter.matchesEntry(currentEntry))
798                {
799                  throw new DirectoryException(ResultCode.ASSERTION_FAILED,
800                                               ERR_MODDN_ASSERTION_FAILED.get(
801                                                    String.valueOf(entryDN)));
802                }
803              }
804              catch (DirectoryException de)
805              {
806                if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
807                {
808                  throw de;
809                }
810    
811                if (debugEnabled())
812                {
813                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
814                }
815    
816                throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
817                               ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
818                                    String.valueOf(entryDN),
819                                    de.getMessageObject()));
820              }
821            }
822            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
823            {
824              noOp = true;
825            }
826            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
827            {
828              if (c instanceof LDAPPreReadRequestControl)
829              {
830                preReadRequest = (LDAPPreReadRequestControl) c;
831              }
832              else
833              {
834                try
835                {
836                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
837                  requestControls.set(i, preReadRequest);
838                }
839                catch (LDAPException le)
840                {
841                  if (debugEnabled())
842                  {
843                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
844                  }
845    
846                  throw new DirectoryException(
847                                 ResultCode.valueOf(le.getResultCode()),
848                                 le.getMessageObject());
849                }
850              }
851            }
852            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
853            {
854              if (c instanceof LDAPPostReadRequestControl)
855              {
856                postReadRequest = (LDAPPostReadRequestControl) c;
857              }
858              else
859              {
860                try
861                {
862                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
863                  requestControls.set(i, postReadRequest);
864                }
865                catch (LDAPException le)
866                {
867                  if (debugEnabled())
868                  {
869                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
870                  }
871    
872                  throw new DirectoryException(
873                                 ResultCode.valueOf(le.getResultCode()),
874                                 le.getMessageObject());
875                }
876              }
877            }
878            else if (oid.equals(OID_PROXIED_AUTH_V1))
879            {
880              // The requester must have the PROXIED_AUTH privilige in order to
881              // be able to use this control.
882              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
883              {
884                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
885                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
886              }
887    
888    
889              ProxiedAuthV1Control proxyControl;
890              if (c instanceof ProxiedAuthV1Control)
891              {
892                proxyControl = (ProxiedAuthV1Control) c;
893              }
894              else
895              {
896                try
897                {
898                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
899                }
900                catch (LDAPException le)
901                {
902                  if (debugEnabled())
903                  {
904                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
905                  }
906    
907                  throw new DirectoryException(
908                                 ResultCode.valueOf(le.getResultCode()),
909                                 le.getMessageObject());
910                }
911              }
912    
913    
914              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
915              setAuthorizationEntry(authorizationEntry);
916              if (authorizationEntry == null)
917              {
918                setProxiedAuthorizationDN(DN.nullDN());
919              }
920              else
921              {
922                setProxiedAuthorizationDN(authorizationEntry.getDN());
923              }
924            }
925            else if (oid.equals(OID_PROXIED_AUTH_V2))
926            {
927              // The requester must have the PROXIED_AUTH privilige in order to
928              // be able to use this control.
929              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
930              {
931                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
932                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
933              }
934    
935    
936              ProxiedAuthV2Control proxyControl;
937              if (c instanceof ProxiedAuthV2Control)
938              {
939                proxyControl = (ProxiedAuthV2Control) c;
940              }
941              else
942              {
943                try
944                {
945                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
946                }
947                catch (LDAPException le)
948                {
949                  if (debugEnabled())
950                  {
951                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
952                  }
953    
954                  throw new DirectoryException(
955                                 ResultCode.valueOf(le.getResultCode()),
956                                 le.getMessageObject());
957                }
958              }
959    
960    
961              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
962              setAuthorizationEntry(authorizationEntry);
963              if (authorizationEntry == null)
964              {
965                setProxiedAuthorizationDN(DN.nullDN());
966              }
967              else
968              {
969                setProxiedAuthorizationDN(authorizationEntry.getDN());
970              }
971            }
972    
973            // NYI -- Add support for additional controls.
974    
975            else if (c.isCritical())
976            {
977              if ((backend == null) || (! backend.supportsControl(oid)))
978              {
979                throw new DirectoryException(
980                               ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
981                               ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get(
982                                    String.valueOf(entryDN), oid));
983              }
984            }
985          }
986        }
987      }
988    
989    
990    
991      /**
992       * Updates the entry so that its attributes are changed to reflect the changes
993       * to the RDN.  This also performs schema checking on the updated entry.
994       *
995       * @param  modifications  A list to hold the modifications made to the entry.
996       *
997       * @throws  DirectoryException  If a problem occurs that should cause the
998       *                              modify DN operation to fail.
999       */
1000      private void applyRDNChanges(List<Modification> modifications)
1001              throws DirectoryException
1002      {
1003        // If we should delete the old RDN values from the entry, then do so.
1004        if (deleteOldRDN())
1005        {
1006          RDN currentRDN = entryDN.getRDN();
1007          int numValues  = currentRDN.getNumValues();
1008          for (int i=0; i < numValues; i++)
1009          {
1010            LinkedHashSet<AttributeValue> valueSet =
1011                 new LinkedHashSet<AttributeValue>(1);
1012            valueSet.add(currentRDN.getAttributeValue(i));
1013    
1014            Attribute a = new Attribute(currentRDN.getAttributeType(i),
1015                                        currentRDN.getAttributeName(i), valueSet);
1016    
1017            // If the associated attribute type is marked NO-USER-MODIFICATION, then
1018            // refuse the update.
1019            if (a.getAttributeType().isNoUserModification())
1020            {
1021              if (! (isInternalOperation() || isSynchronizationOperation()))
1022              {
1023                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1024                               ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get(
1025                                    String.valueOf(entryDN), a.getName()));
1026              }
1027            }
1028    
1029            LinkedList<AttributeValue> missingValues =
1030                 new LinkedList<AttributeValue>();
1031            newEntry.removeAttribute(a, missingValues);
1032    
1033            if (missingValues.isEmpty())
1034            {
1035              modifications.add(new Modification(ModificationType.DELETE, a));
1036            }
1037          }
1038        }
1039    
1040    
1041        // Add the new RDN values to the entry.
1042        int newRDNValues = newRDN.getNumValues();
1043        for (int i=0; i < newRDNValues; i++)
1044        {
1045          LinkedHashSet<AttributeValue> valueSet =
1046               new LinkedHashSet<AttributeValue>(1);
1047          valueSet.add(newRDN.getAttributeValue(i));
1048    
1049          Attribute a = new Attribute(newRDN.getAttributeType(i),
1050                                      newRDN.getAttributeName(i), valueSet);
1051    
1052          LinkedList<AttributeValue> duplicateValues =
1053               new LinkedList<AttributeValue>();
1054          newEntry.addAttribute(a, duplicateValues);
1055    
1056          if (duplicateValues.isEmpty())
1057          {
1058            // If the associated attribute type is marked NO-USER-MODIFICATION, then
1059            // refuse the update.
1060            if (a.getAttributeType().isNoUserModification())
1061            {
1062              if (! (isInternalOperation() || isSynchronizationOperation()))
1063              {
1064                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1065                               ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get(
1066                                    String.valueOf(entryDN), a.getName()));
1067              }
1068            }
1069            else
1070            {
1071              modifications.add(new Modification(ModificationType.ADD, a));
1072            }
1073          }
1074        }
1075    
1076        // If the server is configured to check the schema and the operation is not
1077        // a synchronization operation, make sure that the resulting entry is valid
1078        // as per the server schema.
1079        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
1080        {
1081          MessageBuilder invalidReason = new MessageBuilder();
1082          if (! newEntry.conformsToSchema(null, false, true, true,
1083                                          invalidReason))
1084          {
1085            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
1086                                         ERR_MODDN_VIOLATES_SCHEMA.get(
1087                                              String.valueOf(entryDN),
1088                                              String.valueOf(invalidReason)));
1089          }
1090    
1091          for (int i=0; i < newRDNValues; i++)
1092          {
1093            AttributeType at = newRDN.getAttributeType(i);
1094            if (at.isObsolete())
1095            {
1096              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1097                                           ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get(
1098                                                String.valueOf(entryDN),
1099                                                at.getNameOrOID()));
1100            }
1101          }
1102        }
1103      }
1104    
1105    
1106    
1107      /**
1108       * Applies any modifications performed during pre-operation plugin processing.
1109       * This also performs schema checking for the updated entry.
1110       *
1111       * @param  modifications  A list containing the modifications made to the
1112       *                        entry.
1113       * @param  startPos       The position in the list at which the pre-operation
1114       *                        modifications start.
1115       *
1116       * @throws  DirectoryException  If a problem occurs that should cause the
1117       *                              modify DN operation to fail.
1118       */
1119      private void applyPreOpModifications(List<Modification> modifications,
1120                                           int startPos)
1121              throws DirectoryException
1122      {
1123        for (int i=startPos; i < modifications.size(); i++)
1124        {
1125          Modification m = modifications.get(i);
1126          Attribute    a = m.getAttribute();
1127    
1128          switch (m.getModificationType())
1129          {
1130            case ADD:
1131              LinkedList<AttributeValue> duplicateValues =
1132                   new LinkedList<AttributeValue>();
1133              newEntry.addAttribute(a, duplicateValues);
1134              break;
1135    
1136            case DELETE:
1137              LinkedList<AttributeValue> missingValues =
1138                   new LinkedList<AttributeValue>();
1139              newEntry.removeAttribute(a, missingValues);
1140              break;
1141    
1142            case REPLACE:
1143              duplicateValues = new LinkedList<AttributeValue>();
1144              newEntry.removeAttribute(a.getAttributeType(), a.getOptions());
1145              newEntry.addAttribute(a, duplicateValues);
1146              break;
1147    
1148            case INCREMENT:
1149              List<Attribute> attrList =
1150                   newEntry.getAttribute(a.getAttributeType(),
1151                                         a.getOptions());
1152              if ((attrList == null) || attrList.isEmpty())
1153              {
1154                throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
1155                                             ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
1156                                                  String.valueOf(entryDN),
1157                                                  a.getName()));
1158              }
1159              else if (attrList.size() > 1)
1160              {
1161                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1162                               ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
1163                                    String.valueOf(entryDN), a.getName()));
1164              }
1165    
1166              LinkedHashSet<AttributeValue> values =
1167                   attrList.get(0).getValues();
1168              if ((values == null) || values.isEmpty())
1169              {
1170                throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
1171                                             ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
1172                                                  String.valueOf(entryDN),
1173                                                  a.getName()));
1174              }
1175              else if (values.size() > 1)
1176              {
1177                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1178                               ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
1179                                    String.valueOf(entryDN), a.getName()));
1180              }
1181    
1182              long currentLongValue;
1183              try
1184              {
1185                AttributeValue v = values.iterator().next();
1186                currentLongValue = Long.parseLong(v.getStringValue());
1187              }
1188              catch (Exception e)
1189              {
1190                if (debugEnabled())
1191                {
1192                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
1193                }
1194    
1195                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1196                               ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get(
1197                                    String.valueOf(entryDN), a.getName()));
1198              }
1199    
1200              LinkedHashSet<AttributeValue> newValues = a.getValues();
1201              if ((newValues == null) || newValues.isEmpty())
1202              {
1203                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1204                               ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get(
1205                                    String.valueOf(entryDN), a.getName()));
1206              }
1207              else if (newValues.size() > 1)
1208              {
1209                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1210                               ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get(
1211                                    String.valueOf(entryDN), a.getName()));
1212              }
1213    
1214              long incrementAmount;
1215              try
1216              {
1217                AttributeValue v = values.iterator().next();
1218                incrementAmount = Long.parseLong(v.getStringValue());
1219              }
1220              catch (Exception e)
1221              {
1222                if (debugEnabled())
1223                {
1224                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
1225                }
1226    
1227                throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1228                               ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get(
1229                                    String.valueOf(entryDN), a.getName()));
1230              }
1231    
1232              long newLongValue = currentLongValue + incrementAmount;
1233              ByteString newValueOS =
1234                   new ASN1OctetString(String.valueOf(newLongValue));
1235    
1236              newValues = new LinkedHashSet<AttributeValue>(1);
1237              newValues.add(new AttributeValue(a.getAttributeType(),
1238                                               newValueOS));
1239    
1240              List<Attribute> newAttrList = new ArrayList<Attribute>(1);
1241              newAttrList.add(new Attribute(a.getAttributeType(),
1242                                            a.getName(), newValues));
1243              newEntry.putAttribute(a.getAttributeType(), newAttrList);
1244    
1245              break;
1246          }
1247        }
1248    
1249    
1250        // Make sure that the updated entry still conforms to the server
1251        // schema.
1252        if (DirectoryServer.checkSchema())
1253        {
1254          MessageBuilder invalidReason = new MessageBuilder();
1255          if (! newEntry.conformsToSchema(null, false, true, true,
1256                                          invalidReason))
1257          {
1258            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
1259                                         ERR_MODDN_PREOP_VIOLATES_SCHEMA.get(
1260                                              String.valueOf(entryDN),
1261                                              String.valueOf(invalidReason)));
1262          }
1263        }
1264      }
1265    
1266    
1267    
1268      /**
1269       * Performs any necessary processing to create the pre-read and/or post-read
1270       * response controls and attach them to the response.
1271       */
1272      private void processReadEntryControls()
1273      {
1274        if (preReadRequest != null)
1275        {
1276          Entry entry = currentEntry.duplicate(true);
1277    
1278          if (! preReadRequest.allowsAttribute(
1279                     DirectoryServer.getObjectClassAttributeType()))
1280          {
1281            entry.removeAttribute(
1282                 DirectoryServer.getObjectClassAttributeType());
1283          }
1284    
1285          if (! preReadRequest.returnAllUserAttributes())
1286          {
1287            Iterator<AttributeType> iterator =
1288                 entry.getUserAttributes().keySet().iterator();
1289            while (iterator.hasNext())
1290            {
1291              AttributeType attrType = iterator.next();
1292              if (! preReadRequest.allowsAttribute(attrType))
1293              {
1294                iterator.remove();
1295              }
1296            }
1297          }
1298    
1299          if (! preReadRequest.returnAllOperationalAttributes())
1300          {
1301            Iterator<AttributeType> iterator =
1302                 entry.getOperationalAttributes().keySet().iterator();
1303            while (iterator.hasNext())
1304            {
1305              AttributeType attrType = iterator.next();
1306              if (! preReadRequest.allowsAttribute(attrType))
1307              {
1308                iterator.remove();
1309              }
1310            }
1311          }
1312    
1313          // FIXME -- Check access controls on the entry to see if it should
1314          //          be returned or if any attributes need to be stripped
1315          //          out..
1316          SearchResultEntry searchEntry = new SearchResultEntry(entry);
1317          LDAPPreReadResponseControl responseControl =
1318               new LDAPPreReadResponseControl(preReadRequest.getOID(),
1319                                              preReadRequest.isCritical(),
1320                                              searchEntry);
1321    
1322          addResponseControl(responseControl);
1323        }
1324    
1325        if (postReadRequest != null)
1326        {
1327          Entry entry = newEntry.duplicate(true);
1328    
1329          if (! postReadRequest.allowsAttribute(
1330                     DirectoryServer.getObjectClassAttributeType()))
1331          {
1332            entry.removeAttribute(
1333                 DirectoryServer.getObjectClassAttributeType());
1334          }
1335    
1336          if (! postReadRequest.returnAllUserAttributes())
1337          {
1338            Iterator<AttributeType> iterator =
1339                 entry.getUserAttributes().keySet().iterator();
1340            while (iterator.hasNext())
1341            {
1342              AttributeType attrType = iterator.next();
1343              if (! postReadRequest.allowsAttribute(attrType))
1344              {
1345                iterator.remove();
1346              }
1347            }
1348          }
1349    
1350          if (! postReadRequest.returnAllOperationalAttributes())
1351          {
1352            Iterator<AttributeType> iterator =
1353                 entry.getOperationalAttributes().keySet().iterator();
1354            while (iterator.hasNext())
1355            {
1356              AttributeType attrType = iterator.next();
1357              if (! postReadRequest.allowsAttribute(attrType))
1358              {
1359                iterator.remove();
1360              }
1361            }
1362          }
1363    
1364          // FIXME -- Check access controls on the entry to see if it should
1365          //          be returned or if any attributes need to be stripped
1366          //          out..
1367          SearchResultEntry searchEntry = new SearchResultEntry(entry);
1368          LDAPPostReadResponseControl responseControl =
1369               new LDAPPostReadResponseControl(postReadRequest.getOID(),
1370                                               postReadRequest.isCritical(),
1371                                               searchEntry);
1372    
1373          addResponseControl(responseControl);
1374        }
1375      }
1376    }
1377