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.Iterator;
032    import java.util.List;
033    import java.util.concurrent.locks.Lock;
034    
035    import org.opends.messages.Message;
036    import org.opends.server.api.Backend;
037    import org.opends.server.api.ChangeNotificationListener;
038    import org.opends.server.api.ClientConnection;
039    import org.opends.server.api.SynchronizationProvider;
040    import org.opends.server.api.plugin.PluginResult;
041    import org.opends.server.controls.LDAPAssertionRequestControl;
042    import org.opends.server.controls.LDAPPreReadRequestControl;
043    import org.opends.server.controls.LDAPPreReadResponseControl;
044    import org.opends.server.controls.ProxiedAuthV1Control;
045    import org.opends.server.controls.ProxiedAuthV2Control;
046    import org.opends.server.core.AccessControlConfigManager;
047    import org.opends.server.core.DeleteOperationWrapper;
048    import org.opends.server.core.DeleteOperation;
049    import org.opends.server.core.DirectoryServer;
050    import org.opends.server.core.PluginConfigManager;
051    import org.opends.server.loggers.debug.DebugTracer;
052    import org.opends.server.types.AttributeType;
053    import org.opends.server.types.CanceledOperationException;
054    import org.opends.server.types.Control;
055    import org.opends.server.types.DebugLogLevel;
056    import org.opends.server.types.DirectoryException;
057    import org.opends.server.types.DN;
058    import org.opends.server.types.Entry;
059    import org.opends.server.types.LDAPException;
060    import org.opends.server.types.LockManager;
061    import org.opends.server.types.Privilege;
062    import org.opends.server.types.ResultCode;
063    import org.opends.server.types.SearchFilter;
064    import org.opends.server.types.SearchResultEntry;
065    import org.opends.server.types.SynchronizationProviderResult;
066    import org.opends.server.types.operation.PostOperationDeleteOperation;
067    import org.opends.server.types.operation.PostResponseDeleteOperation;
068    import org.opends.server.types.operation.PreOperationDeleteOperation;
069    import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
070    
071    import static org.opends.messages.CoreMessages.*;
072    import static org.opends.server.loggers.ErrorLogger.*;
073    import static org.opends.server.loggers.debug.DebugLogger.*;
074    import static org.opends.server.util.ServerConstants.*;
075    import static org.opends.server.util.StaticUtils.*;
076    
077    
078    
079    /**
080     * This class defines an operation used to delete an entry in a local backend
081     * of the Directory Server.
082     */
083    public class LocalBackendDeleteOperation
084           extends DeleteOperationWrapper
085           implements PreOperationDeleteOperation, PostOperationDeleteOperation,
086                      PostResponseDeleteOperation,
087                      PostSynchronizationDeleteOperation
088    {
089      /**
090       * The tracer object for the debug logger.
091       */
092      private static final DebugTracer TRACER = getTracer();
093    
094    
095    
096      // The backend in which the operation is to be processed.
097      private Backend backend;
098    
099      // Indicates whether the LDAP no-op control has been requested.
100      private boolean noOp;
101    
102      // The client connection on which this operation was requested.
103      private ClientConnection clientConnection;
104    
105      // The DN of the entry to be deleted.
106      private DN entryDN;
107    
108      // The entry to be deleted.
109      private Entry entry;
110    
111      // The pre-read request control included in the request, if applicable.
112      private LDAPPreReadRequestControl preReadRequest;
113    
114    
115    
116      /**
117       * Creates a new operation that may be used to delete an entry from a
118       * local backend of the Directory Server.
119       *
120       * @param delete The operation to enhance.
121       */
122      public LocalBackendDeleteOperation(DeleteOperation delete)
123      {
124        super(delete);
125        LocalBackendWorkflowElement.attachLocalOperation (delete, this);
126      }
127    
128    
129    
130      /**
131       * Retrieves the entry to be deleted.
132       *
133       * @return  The entry to be deleted, or <CODE>null</CODE> if the entry is not
134       *          yet available.
135       */
136      public Entry getEntryToDelete()
137      {
138        return entry;
139      }
140    
141    
142    
143      /**
144       * Process this delete operation in a local backend.
145       *
146       * @param  backend  The backend in which the delete operation should be
147       *                  processed.
148       *
149       * @throws CanceledOperationException if this operation should be
150       * cancelled
151       */
152      void processLocalDelete(Backend backend) throws CanceledOperationException {
153        boolean executePostOpPlugins = false;
154    
155        this.backend = backend;
156    
157        clientConnection = getClientConnection();
158    
159        // Get the plugin config manager that will be used for invoking plugins.
160        PluginConfigManager pluginConfigManager =
161             DirectoryServer.getPluginConfigManager();
162    
163        // Check for a request to cancel this operation.
164        checkIfCanceled(false);
165    
166        // Create a labeled block of code that we can break out of if a problem is
167        // detected.
168    deleteProcessing:
169        {
170          // Process the entry DN to convert it from its raw form as provided by the
171          // client to the form required for the rest of the delete processing.
172          entryDN = getEntryDN();
173          if (entryDN == null){
174            break deleteProcessing;
175          }
176    
177          // Grab a write lock on the entry.
178          Lock entryLock = null;
179          for (int i=0; i < 3; i++)
180          {
181            entryLock = LockManager.lockWrite(entryDN);
182            if (entryLock != null)
183            {
184              break;
185            }
186          }
187    
188          if (entryLock == null)
189          {
190            setResultCode(DirectoryServer.getServerErrorResultCode());
191            appendErrorMessage(ERR_DELETE_CANNOT_LOCK_ENTRY.get(
192                                    String.valueOf(entryDN)));
193            break deleteProcessing;
194          }
195    
196          try
197          {
198            // Get the entry to delete.  If it doesn't exist, then fail.
199            try
200            {
201              entry = backend.getEntry(entryDN);
202              if (entry == null)
203              {
204                setResultCode(ResultCode.NO_SUCH_OBJECT);
205                appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
206                                        String.valueOf(entryDN)));
207    
208                try
209                {
210                  DN parentDN = entryDN.getParentDNInSuffix();
211                  while (parentDN != null)
212                  {
213                    if (DirectoryServer.entryExists(parentDN))
214                    {
215                      setMatchedDN(parentDN);
216                      break;
217                    }
218    
219                    parentDN = parentDN.getParentDNInSuffix();
220                  }
221                }
222                catch (Exception e)
223                {
224                  if (debugEnabled())
225                  {
226                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
227                  }
228                }
229    
230                break deleteProcessing;
231              }
232            }
233            catch (DirectoryException de)
234            {
235              if (debugEnabled())
236              {
237                TRACER.debugCaught(DebugLogLevel.ERROR, de);
238              }
239    
240              setResponseData(de);
241              break deleteProcessing;
242            }
243    
244    
245            // Invoke any conflict resolution processing that might be needed by the
246            // synchronization provider.
247            for (SynchronizationProvider provider :
248                 DirectoryServer.getSynchronizationProviders())
249            {
250              try
251              {
252                SynchronizationProviderResult result =
253                     provider.handleConflictResolution(this);
254                if (! result.continueProcessing())
255                {
256                  setResultCode(result.getResultCode());
257                  appendErrorMessage(result.getErrorMessage());
258                  setMatchedDN(result.getMatchedDN());
259                  setReferralURLs(result.getReferralURLs());
260                  break deleteProcessing;
261                }
262              }
263              catch (DirectoryException de)
264              {
265                if (debugEnabled())
266                {
267                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
268                }
269    
270                logError(ERR_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
271                              getConnectionID(), getOperationID(),
272                              getExceptionMessage(de)));
273                setResponseData(de);
274                break deleteProcessing;
275              }
276            }
277    
278            // Check to see if the client has permission to perform the
279            // delete.
280    
281            // Check to see if there are any controls in the request.  If so, then
282            // see if there is any special processing required.
283            try
284            {
285              handleRequestControls();
286            }
287            catch (DirectoryException de)
288            {
289              if (debugEnabled())
290              {
291                TRACER.debugCaught(DebugLogLevel.ERROR, de);
292              }
293    
294              setResponseData(de);
295              break deleteProcessing;
296            }
297    
298    
299            // FIXME: for now assume that this will check all permission
300            // pertinent to the operation. This includes proxy authorization
301            // and any other controls specified.
302    
303            // FIXME: earlier checks to see if the entry already exists may
304            // have already exposed sensitive information to the client.
305            if (! AccessControlConfigManager.getInstance().
306                       getAccessControlHandler().isAllowed(this))
307            {
308              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
309              appendErrorMessage(ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
310                                      String.valueOf(entryDN)));
311              break deleteProcessing;
312            }
313    
314            // Check for a request to cancel this operation.
315            checkIfCanceled(false);
316    
317    
318            // If the operation is not a synchronization operation,
319            // invoke the pre-delete plugins.
320            if (! isSynchronizationOperation())
321            {
322              executePostOpPlugins = true;
323              PluginResult.PreOperation preOpResult =
324                   pluginConfigManager.invokePreOperationDeletePlugins(this);
325              if (!preOpResult.continueProcessing())
326              {
327                setResultCode(preOpResult.getResultCode());
328                appendErrorMessage(preOpResult.getErrorMessage());
329                setMatchedDN(preOpResult.getMatchedDN());
330                setReferralURLs(preOpResult.getReferralURLs());
331                break deleteProcessing;
332              }
333            }
334    
335    
336            // Check for a request to cancel this operation.
337            checkIfCanceled(true);
338    
339    
340            // Get the backend to use for the delete.  If there is none, then fail.
341            if (backend == null)
342            {
343              setResultCode(ResultCode.NO_SUCH_OBJECT);
344              appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
345                                      String.valueOf(entryDN)));
346              break deleteProcessing;
347            }
348    
349    
350            // If it is not a private backend, then check to see if the server or
351            // backend is operating in read-only mode.
352            if (! backend.isPrivateBackend())
353            {
354              switch (DirectoryServer.getWritabilityMode())
355              {
356                case DISABLED:
357                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
358                  appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
359                                          String.valueOf(entryDN)));
360                  break deleteProcessing;
361    
362                case INTERNAL_ONLY:
363                  if (! (isInternalOperation() || isSynchronizationOperation()))
364                  {
365                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
366                    appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
367                                            String.valueOf(entryDN)));
368                    break deleteProcessing;
369                  }
370              }
371    
372              switch (backend.getWritabilityMode())
373              {
374                case DISABLED:
375                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
376                  appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
377                                          String.valueOf(entryDN)));
378                  break deleteProcessing;
379    
380                case INTERNAL_ONLY:
381                  if (! (isInternalOperation() || isSynchronizationOperation()))
382                  {
383                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
384                    appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
385                                            String.valueOf(entryDN)));
386                    break deleteProcessing;
387                  }
388              }
389            }
390    
391    
392            // The selected backend will have the responsibility of making sure that
393            // the entry actually exists and does not have any children (or possibly
394            // handling a subtree delete).  But we will need to check if there are
395            // any subordinate backends that should stop us from attempting the
396            // delete.
397            Backend[] subBackends = backend.getSubordinateBackends();
398            for (Backend b : subBackends)
399            {
400              DN[] baseDNs = b.getBaseDNs();
401              for (DN dn : baseDNs)
402              {
403                if (dn.isDescendantOf(entryDN))
404                {
405                  setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
406                  appendErrorMessage(ERR_DELETE_HAS_SUB_BACKEND.get(
407                                          String.valueOf(entryDN),
408                                          String.valueOf(dn)));
409                  break deleteProcessing;
410                }
411              }
412            }
413    
414    
415            // Actually perform the delete.
416            try
417            {
418              if (noOp)
419              {
420                setResultCode(ResultCode.NO_OPERATION);
421                appendErrorMessage(INFO_DELETE_NOOP.get());
422              }
423              else
424              {
425                for (SynchronizationProvider provider :
426                     DirectoryServer.getSynchronizationProviders())
427                {
428                  try
429                  {
430                    SynchronizationProviderResult result =
431                        provider.doPreOperation(this);
432                    if (! result.continueProcessing())
433                    {
434                      setResultCode(result.getResultCode());
435                      appendErrorMessage(result.getErrorMessage());
436                      setMatchedDN(result.getMatchedDN());
437                      setReferralURLs(result.getReferralURLs());
438                      break deleteProcessing;
439                    }
440                  }
441                  catch (DirectoryException de)
442                  {
443                    if (debugEnabled())
444                    {
445                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
446                    }
447    
448                    logError(ERR_DELETE_SYNCH_PREOP_FAILED.get(getConnectionID(),
449                                  getOperationID(), getExceptionMessage(de)));
450                    setResponseData(de);
451                    break deleteProcessing;
452                  }
453                }
454    
455                backend.deleteEntry(entryDN, this);
456              }
457    
458    
459              processPreReadControl();
460    
461    
462              if (! noOp)
463              {
464                setResultCode(ResultCode.SUCCESS);
465              }
466            }
467            catch (DirectoryException de)
468            {
469              if (debugEnabled())
470              {
471                TRACER.debugCaught(DebugLogLevel.ERROR, de);
472              }
473    
474              setResponseData(de);
475              break deleteProcessing;
476            }
477          }
478          finally
479          {
480            LockManager.unlock(entryDN, entryLock);
481          }
482        }
483    
484    
485        for (SynchronizationProvider provider :
486            DirectoryServer.getSynchronizationProviders())
487        {
488          try
489          {
490            provider.doPostOperation(this);
491          }
492          catch (DirectoryException de)
493          {
494            if (debugEnabled())
495            {
496              TRACER.debugCaught(DebugLogLevel.ERROR, de);
497            }
498    
499            logError(ERR_DELETE_SYNCH_POSTOP_FAILED.get(getConnectionID(),
500                getOperationID(), getExceptionMessage(de)));
501            setResponseData(de);
502            break;
503          }
504        }
505    
506        // Invoke the post-operation or post-synchronization delete plugins.
507        if (isSynchronizationOperation())
508        {
509          if (getResultCode() == ResultCode.SUCCESS)
510          {
511            pluginConfigManager.invokePostSynchronizationDeletePlugins(this);
512          }
513        }
514        else if (executePostOpPlugins)
515        {
516          PluginResult.PostOperation postOpResult =
517              pluginConfigManager.invokePostOperationDeletePlugins(this);
518          if (!postOpResult.continueProcessing())
519          {
520            setResultCode(postOpResult.getResultCode());
521            appendErrorMessage(postOpResult.getErrorMessage());
522            setMatchedDN(postOpResult.getMatchedDN());
523            setReferralURLs(postOpResult.getReferralURLs());
524            return;
525          }
526        }
527    
528    
529        // Notify any change notification listeners that might be registered with
530        // the server.
531        if (getResultCode() == ResultCode.SUCCESS)
532        {
533          for (ChangeNotificationListener changeListener :
534               DirectoryServer.getChangeNotificationListeners())
535          {
536            try
537            {
538              changeListener.handleDeleteOperation(this, entry);
539            }
540            catch (Exception e)
541            {
542              if (debugEnabled())
543              {
544                TRACER.debugCaught(DebugLogLevel.ERROR, e);
545              }
546    
547              Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get(
548                  getExceptionMessage(e));
549              logError(message);
550            }
551          }
552        }
553      }
554    
555    
556    
557      /**
558       * Performs any request control processing needed for this operation.
559       *
560       * @throws  DirectoryException  If a problem occurs that should cause the
561       *                              operation to fail.
562       */
563      private void handleRequestControls()
564              throws DirectoryException
565      {
566        List<Control> requestControls = getRequestControls();
567        if ((requestControls != null) && (! requestControls.isEmpty()))
568        {
569          for (int i=0; i < requestControls.size(); i++)
570          {
571            Control c   = requestControls.get(i);
572            String  oid = c.getOID();
573    
574            if (!AccessControlConfigManager.getInstance().
575                     getAccessControlHandler().isAllowed(entryDN, this, c))
576            {
577              throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
578                             ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
579            }
580    
581            if (oid.equals(OID_LDAP_ASSERTION))
582            {
583              LDAPAssertionRequestControl assertControl;
584              if (c instanceof LDAPAssertionRequestControl)
585              {
586                assertControl = (LDAPAssertionRequestControl) c;
587              }
588              else
589              {
590                try
591                {
592                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
593                  requestControls.set(i, assertControl);
594                }
595                catch (LDAPException le)
596                {
597                  if (debugEnabled())
598                  {
599                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
600                  }
601    
602                  throw new DirectoryException(
603                                 ResultCode.valueOf(le.getResultCode()),
604                                 le.getMessageObject());
605                }
606              }
607    
608              try
609              {
610                // FIXME -- We need to determine whether the current user has
611                //          permission to make this determination.
612                SearchFilter filter = assertControl.getSearchFilter();
613                if (! filter.matchesEntry(entry))
614                {
615                  throw new DirectoryException(ResultCode.ASSERTION_FAILED,
616                                               ERR_DELETE_ASSERTION_FAILED.get(
617                                                    String.valueOf(entryDN)));
618                }
619              }
620              catch (DirectoryException de)
621              {
622                if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
623                {
624                  throw de;
625                }
626    
627                if (debugEnabled())
628                {
629                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
630                }
631    
632                throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
633                               ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get(
634                                    String.valueOf(entryDN),
635                                    de.getMessageObject()));
636              }
637            }
638            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
639            {
640              noOp = true;
641            }
642            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
643            {
644              if (c instanceof LDAPPreReadRequestControl)
645              {
646                preReadRequest = (LDAPPreReadRequestControl) c;
647              }
648              else
649              {
650                try
651                {
652                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
653                  requestControls.set(i, preReadRequest);
654                }
655                catch (LDAPException le)
656                {
657                  if (debugEnabled())
658                  {
659                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
660                  }
661    
662                  throw new DirectoryException(
663                                 ResultCode.valueOf(le.getResultCode()),
664                                 le.getMessageObject());
665                }
666              }
667            }
668            else if (oid.equals(OID_PROXIED_AUTH_V1))
669            {
670              // The requester must have the PROXIED_AUTH privilige in order to
671              // be able to use this control.
672              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
673              {
674                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
675                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
676              }
677    
678    
679              ProxiedAuthV1Control proxyControl;
680              if (c instanceof ProxiedAuthV1Control)
681              {
682                proxyControl = (ProxiedAuthV1Control) c;
683              }
684              else
685              {
686                try
687                {
688                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
689                }
690                catch (LDAPException le)
691                {
692                  if (debugEnabled())
693                  {
694                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
695                  }
696    
697                  throw new DirectoryException(
698                                 ResultCode.valueOf(le.getResultCode()),
699                                 le.getMessageObject());
700                }
701              }
702    
703    
704              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
705              setAuthorizationEntry(authorizationEntry);
706              if (authorizationEntry == null)
707              {
708                setProxiedAuthorizationDN(DN.nullDN());
709              }
710              else
711              {
712                setProxiedAuthorizationDN(authorizationEntry.getDN());
713              }
714            }
715            else if (oid.equals(OID_PROXIED_AUTH_V2))
716            {
717              // The requester must have the PROXIED_AUTH privilige in order to
718              // be able to use this control.
719              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
720              {
721                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
722                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
723              }
724    
725    
726              ProxiedAuthV2Control proxyControl;
727              if (c instanceof ProxiedAuthV2Control)
728              {
729                proxyControl = (ProxiedAuthV2Control) c;
730              }
731              else
732              {
733                try
734                {
735                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
736                }
737                catch (LDAPException le)
738                {
739                  if (debugEnabled())
740                  {
741                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
742                  }
743    
744                  throw new DirectoryException(
745                                 ResultCode.valueOf(le.getResultCode()),
746                                 le.getMessageObject());
747                }
748              }
749    
750    
751              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
752              setAuthorizationEntry(authorizationEntry);
753              if (authorizationEntry == null)
754              {
755                setProxiedAuthorizationDN(DN.nullDN());
756              }
757              else
758              {
759                setProxiedAuthorizationDN(authorizationEntry.getDN());
760              }
761            }
762    
763            // NYI -- Add support for additional controls.
764    
765            else if (c.isCritical())
766            {
767              if ((backend == null) || (! backend.supportsControl(oid)))
768              {
769                throw new DirectoryException(
770                               ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
771                               ERR_DELETE_UNSUPPORTED_CRITICAL_CONTROL.get(
772                                    String.valueOf(entryDN), oid));
773              }
774            }
775          }
776        }
777      }
778    
779    
780    
781      /**
782       * Performs any processing needed for the LDAP pre-read control.
783       */
784      private void processPreReadControl()
785      {
786        if (preReadRequest != null)
787        {
788          Entry entryCopy = entry.duplicate(true);
789    
790          if (! preReadRequest.allowsAttribute(
791                     DirectoryServer.getObjectClassAttributeType()))
792          {
793            entryCopy.removeAttribute(
794                 DirectoryServer.getObjectClassAttributeType());
795          }
796    
797          if (! preReadRequest.returnAllUserAttributes())
798          {
799            Iterator<AttributeType> iterator =
800                 entryCopy.getUserAttributes().keySet().iterator();
801            while (iterator.hasNext())
802            {
803              AttributeType attrType = iterator.next();
804              if (! preReadRequest.allowsAttribute(attrType))
805              {
806                iterator.remove();
807              }
808            }
809          }
810    
811          if (! preReadRequest.returnAllOperationalAttributes())
812          {
813            Iterator<AttributeType> iterator =
814                 entryCopy.getOperationalAttributes().keySet().iterator();
815            while (iterator.hasNext())
816            {
817              AttributeType attrType = iterator.next();
818              if (! preReadRequest.allowsAttribute(attrType))
819              {
820                iterator.remove();
821              }
822            }
823          }
824    
825          // FIXME -- Check access controls on the entry to see if it should
826          //          be returned or if any attributes need to be stripped
827          //          out..
828          SearchResultEntry searchEntry = new SearchResultEntry(entryCopy);
829          LDAPPreReadResponseControl responseControl =
830               new LDAPPreReadResponseControl(preReadRequest.getOID(),
831                                              preReadRequest.isCritical(),
832                                              searchEntry);
833          addResponseControl(responseControl);
834        }
835      }
836    }
837