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 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    
029    import org.opends.messages.MessageBuilder;
030    import org.opends.messages.Message;
031    
032    import java.util.ArrayList;
033    import java.util.HashSet;
034    import java.util.Iterator;
035    import java.util.LinkedHashSet;
036    import java.util.List;
037    import java.util.concurrent.atomic.AtomicBoolean;
038    import org.opends.server.api.ClientConnection;
039    import org.opends.server.api.plugin.PluginResult;
040    import org.opends.server.controls.AccountUsableResponseControl;
041    import org.opends.server.controls.MatchedValuesControl;
042    import org.opends.server.loggers.debug.DebugLogger;
043    import org.opends.server.loggers.debug.DebugTracer;
044    import org.opends.server.protocols.asn1.ASN1OctetString;
045    import org.opends.server.protocols.ldap.LDAPFilter;
046    import org.opends.server.types.*;
047    import org.opends.server.types.operation.PostResponseSearchOperation;
048    import org.opends.server.types.operation.PreParseSearchOperation;
049    import org.opends.server.types.operation.SearchEntrySearchOperation;
050    import org.opends.server.types.operation.SearchReferenceSearchOperation;
051    import org.opends.server.util.TimeThread;
052    
053    import static org.opends.server.core.CoreConstants.*;
054    import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
055    import static org.opends.server.loggers.AccessLogger.*;
056    import static org.opends.messages.CoreMessages.*;
057    import static org.opends.server.util.StaticUtils.toLowerCase;
058    
059    /**
060     * This class defines an operation that may be used to locate entries in the
061     * Directory Server based on a given set of criteria.
062     */
063    public class SearchOperationBasis
064           extends AbstractOperation
065           implements PreParseSearchOperation,
066                      PostResponseSearchOperation,
067                      SearchEntrySearchOperation,
068                      SearchReferenceSearchOperation,
069                      SearchOperation
070    {
071      /**
072       * The tracer object for the debug logger.
073       */
074      private static final DebugTracer TRACER = DebugLogger.getTracer();
075    
076      // Indicates whether a search result done response has been sent to the
077      // client.
078      private AtomicBoolean responseSent;
079    
080      // Indicates whether the client is able to handle referrals.
081      private boolean clientAcceptsReferrals;
082    
083      // Indicates whether to include the account usable control with search result
084      // entries.
085      private boolean includeUsableControl;
086    
087      // Indicates whether to only real attributes should be returned.
088      private boolean realAttributesOnly;
089    
090      // Indicates whether LDAP subentries should be returned.
091      private boolean returnLDAPSubentries;
092    
093      // Indicates whether to include attribute types only or both types and values.
094      private boolean typesOnly;
095    
096      // Indicates whether to only virtual attributes should be returned.
097      private boolean virtualAttributesOnly;
098    
099      // The raw, unprocessed base DN as included in the request from the client.
100      private ByteString rawBaseDN;
101    
102      // The dereferencing policy for the search operation.
103      private DereferencePolicy derefPolicy;
104    
105      // The base DN for the search operation.
106      private DN baseDN;
107    
108      // The proxied authorization target DN for this operation.
109      private DN proxiedAuthorizationDN;
110    
111      // The number of entries that have been sent to the client.
112      private int entriesSent;
113    
114      // The number of search result references that have been sent to the client.
115      private int referencesSent;
116    
117      // The size limit for the search operation.
118      private int sizeLimit;
119    
120      // The time limit for the search operation.
121      private int timeLimit;
122    
123      // The raw, unprocessed filter as included in the request from the client.
124      private RawFilter rawFilter;
125    
126      // The set of attributes that should be returned in matching entries.
127      private LinkedHashSet<String> attributes;
128    
129      // The set of response controls for this search operation.
130      private List<Control> responseControls;
131    
132      // The time that processing started on this operation.
133      private long processingStartTime;
134    
135      // The time that processing ended on this operation.
136      private long processingStopTime;
137    
138      // The time that the search time limit has expired.
139      private long timeLimitExpiration;
140    
141      // The matched values control associated with this search operation.
142      private MatchedValuesControl matchedValuesControl;
143    
144      // The persistent search associated with this search operation.
145      private PersistentSearch persistentSearch;
146    
147      // The search filter for the search operation.
148      private SearchFilter filter;
149    
150      // The search scope for the search operation.
151      private SearchScope scope;
152    
153      // Indicates wether to send the search result done to the client or not
154      private boolean sendResponse = true;
155    
156      /**
157       * Creates a new search operation with the provided information.
158       *
159       * @param  clientConnection  The client connection with which this operation
160       *                           is associated.
161       * @param  operationID       The operation ID for this operation.
162       * @param  messageID         The message ID of the request with which this
163       *                           operation is associated.
164       * @param  requestControls   The set of controls included in the request.
165       * @param  rawBaseDN         The raw, unprocessed base DN as included in the
166       *                           request from the client.
167       * @param  scope             The scope for this search operation.
168       * @param  derefPolicy       The alias dereferencing policy for this search
169       *                           operation.
170       * @param  sizeLimit         The size limit for this search operation.
171       * @param  timeLimit         The time limit for this search operation.
172       * @param  typesOnly         The typesOnly flag for this search operation.
173       * @param  rawFilter         the raw, unprocessed filter as included in the
174       *                           request from the client.
175       * @param  attributes        The requested attributes for this search
176       *                           operation.
177       */
178      public SearchOperationBasis(ClientConnection clientConnection,
179                             long operationID,
180                             int messageID, List<Control> requestControls,
181                             ByteString rawBaseDN, SearchScope scope,
182                             DereferencePolicy derefPolicy, int sizeLimit,
183                             int timeLimit, boolean typesOnly, RawFilter rawFilter,
184                             LinkedHashSet<String> attributes)
185      {
186        super(clientConnection, operationID, messageID, requestControls);
187    
188    
189        this.rawBaseDN   = rawBaseDN;
190        this.scope       = scope;
191        this.derefPolicy = derefPolicy;
192        this.sizeLimit   = sizeLimit;
193        this.timeLimit   = timeLimit;
194        this.typesOnly   = typesOnly;
195        this.rawFilter   = rawFilter;
196    
197        if (attributes == null)
198        {
199          this.attributes  = new LinkedHashSet<String>(0);
200        }
201        else
202        {
203          this.attributes  = attributes;
204        }
205    
206    
207        if (clientConnection.getSizeLimit() <= 0)
208        {
209          this.sizeLimit = sizeLimit;
210        }
211        else
212        {
213          if (sizeLimit <= 0)
214          {
215            this.sizeLimit = clientConnection.getSizeLimit();
216          }
217          else
218          {
219            this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
220          }
221        }
222    
223    
224        if (clientConnection.getTimeLimit() <= 0)
225        {
226          this.timeLimit = timeLimit;
227        }
228        else
229        {
230          if (timeLimit <= 0)
231          {
232            this.timeLimit = clientConnection.getTimeLimit();
233          }
234          else
235          {
236            this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
237          }
238        }
239    
240    
241        baseDN                 = null;
242        filter                 = null;
243        entriesSent            = 0;
244        referencesSent         = 0;
245        responseControls       = new ArrayList<Control>();
246        cancelRequest          = null;
247        clientAcceptsReferrals = true;
248        includeUsableControl   = false;
249        responseSent           = new AtomicBoolean(false);
250        persistentSearch       = null;
251        returnLDAPSubentries   = false;
252        matchedValuesControl   = null;
253        realAttributesOnly     = false;
254        virtualAttributesOnly  = false;
255      }
256    
257    
258    
259      /**
260       * Creates a new search operation with the provided information.
261       *
262       * @param  clientConnection  The client connection with which this operation
263       *                           is associated.
264       * @param  operationID       The operation ID for this operation.
265       * @param  messageID         The message ID of the request with which this
266       *                           operation is associated.
267       * @param  requestControls   The set of controls included in the request.
268       * @param  baseDN            The base DN for this search operation.
269       * @param  scope             The scope for this search operation.
270       * @param  derefPolicy       The alias dereferencing policy for this search
271       *                           operation.
272       * @param  sizeLimit         The size limit for this search operation.
273       * @param  timeLimit         The time limit for this search operation.
274       * @param  typesOnly         The typesOnly flag for this search operation.
275       * @param  filter            The filter for this search operation.
276       * @param  attributes        The attributes for this search operation.
277       */
278      public SearchOperationBasis(ClientConnection clientConnection,
279                             long operationID,
280                             int messageID, List<Control> requestControls,
281                             DN baseDN, SearchScope scope,
282                             DereferencePolicy derefPolicy, int sizeLimit,
283                             int timeLimit, boolean typesOnly, SearchFilter filter,
284                             LinkedHashSet<String> attributes)
285      {
286        super(clientConnection, operationID, messageID, requestControls);
287    
288    
289        this.baseDN      = baseDN;
290        this.scope       = scope;
291        this.derefPolicy = derefPolicy;
292        this.sizeLimit   = sizeLimit;
293        this.timeLimit   = timeLimit;
294        this.typesOnly   = typesOnly;
295        this.filter      = filter;
296    
297        if (attributes == null)
298        {
299          this.attributes = new LinkedHashSet<String>(0);
300        }
301        else
302        {
303          this.attributes  = attributes;
304        }
305    
306        rawBaseDN = new ASN1OctetString(baseDN.toString());
307        rawFilter = new LDAPFilter(filter);
308    
309    
310        if (clientConnection.getSizeLimit() <= 0)
311        {
312          this.sizeLimit = sizeLimit;
313        }
314        else
315        {
316          if (sizeLimit <= 0)
317          {
318            this.sizeLimit = clientConnection.getSizeLimit();
319          }
320          else
321          {
322            this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
323          }
324        }
325    
326    
327        if (clientConnection.getTimeLimit() <= 0)
328        {
329          this.timeLimit = timeLimit;
330        }
331        else
332        {
333          if (timeLimit <= 0)
334          {
335            this.timeLimit = clientConnection.getTimeLimit();
336          }
337          else
338          {
339            this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
340          }
341        }
342    
343    
344        entriesSent            = 0;
345        referencesSent         = 0;
346        responseControls       = new ArrayList<Control>();
347        cancelRequest          = null;
348        clientAcceptsReferrals = true;
349        includeUsableControl   = false;
350        responseSent           = new AtomicBoolean(false);
351        persistentSearch       = null;
352        returnLDAPSubentries   = false;
353        matchedValuesControl   = null;
354      }
355    
356    
357    
358      /**
359       * {@inheritDoc}
360       */
361      public final ByteString getRawBaseDN()
362      {
363        return rawBaseDN;
364      }
365    
366    
367    
368      /**
369       * {@inheritDoc}
370       */
371      public final void setRawBaseDN(ByteString rawBaseDN)
372      {
373        this.rawBaseDN = rawBaseDN;
374    
375        baseDN = null;
376      }
377    
378    
379      /**
380       * {@inheritDoc}
381       */
382      public final DN getBaseDN()
383      {
384        try
385        {
386          if (baseDN == null)
387          {
388            baseDN = DN.decode(rawBaseDN);
389          }
390        }
391        catch (DirectoryException de)
392        {
393          if (debugEnabled())
394          {
395            TRACER.debugCaught(DebugLogLevel.ERROR, de);
396          }
397    
398          setResultCode(de.getResultCode());
399          appendErrorMessage(de.getMessageObject());
400          setMatchedDN(de.getMatchedDN());
401          setReferralURLs(de.getReferralURLs());
402        }
403        return baseDN;
404      }
405    
406    
407      /**
408       * {@inheritDoc}
409       */
410      public final void setBaseDN(DN baseDN)
411      {
412        this.baseDN = baseDN;
413      }
414    
415      /**
416       * {@inheritDoc}
417       */
418      public final SearchScope getScope()
419      {
420        return scope;
421      }
422    
423      /**
424       * {@inheritDoc}
425       */
426      public final void setScope(SearchScope scope)
427      {
428        this.scope = scope;
429      }
430    
431      /**
432       * {@inheritDoc}
433       */
434      public final DereferencePolicy getDerefPolicy()
435      {
436        return derefPolicy;
437      }
438    
439      /**
440       * {@inheritDoc}
441       */
442      public final void setDerefPolicy(DereferencePolicy derefPolicy)
443      {
444        this.derefPolicy = derefPolicy;
445      }
446    
447      /**
448       * {@inheritDoc}
449       */
450      public final int getSizeLimit()
451      {
452        return sizeLimit;
453      }
454    
455      /**
456       * {@inheritDoc}
457       */
458      public final void setSizeLimit(int sizeLimit)
459      {
460        this.sizeLimit = sizeLimit;
461      }
462    
463      /**
464       * {@inheritDoc}
465       */
466      public final int getTimeLimit()
467      {
468        return timeLimit;
469      }
470    
471      /**
472       * {@inheritDoc}
473       */
474      public final void setTimeLimit(int timeLimit)
475      {
476        this.timeLimit = timeLimit;
477      }
478    
479      /**
480       * {@inheritDoc}
481       */
482      public final boolean getTypesOnly()
483      {
484        return typesOnly;
485      }
486    
487      /**
488       * {@inheritDoc}
489       */
490      public final void setTypesOnly(boolean typesOnly)
491      {
492        this.typesOnly = typesOnly;
493      }
494    
495      /**
496       * {@inheritDoc}
497       */
498      public final RawFilter getRawFilter()
499      {
500        return rawFilter;
501      }
502    
503      /**
504       * {@inheritDoc}
505       */
506      public final void setRawFilter(RawFilter rawFilter)
507      {
508        this.rawFilter = rawFilter;
509    
510        filter = null;
511      }
512    
513      /**
514       * {@inheritDoc}
515       */
516      public final SearchFilter getFilter()
517      {
518        try
519        {
520          if (filter == null)
521          {
522            filter = rawFilter.toSearchFilter();
523          }
524        }
525        catch (DirectoryException de)
526        {
527          if (debugEnabled())
528          {
529            TRACER.debugCaught(DebugLogLevel.ERROR, de);
530          }
531    
532          setResultCode(de.getResultCode());
533          appendErrorMessage(de.getMessageObject());
534          setMatchedDN(de.getMatchedDN());
535          setReferralURLs(de.getReferralURLs());
536        }
537        return filter;
538      }
539    
540      /**
541       * {@inheritDoc}
542       */
543      public final LinkedHashSet<String> getAttributes()
544      {
545        return attributes;
546      }
547    
548      /**
549       * {@inheritDoc}
550       */
551      public final void setAttributes(LinkedHashSet<String> attributes)
552      {
553        if (attributes == null)
554        {
555          this.attributes.clear();
556        }
557        else
558        {
559          this.attributes = attributes;
560        }
561      }
562    
563      /**
564       * {@inheritDoc}
565       */
566      public final int getEntriesSent()
567      {
568        return entriesSent;
569      }
570    
571      /**
572       * {@inheritDoc}
573       */
574      public final int getReferencesSent()
575      {
576        return referencesSent;
577      }
578    
579      /**
580       * {@inheritDoc}
581       */
582      public final boolean returnEntry(Entry entry, List<Control> controls)
583      {
584        boolean typesOnly = getTypesOnly();
585        // See if the operation has been abandoned.  If so, then don't send the
586        // entry and indicate that the search should end.
587        if (getCancelRequest() != null)
588        {
589          setResultCode(ResultCode.CANCELED);
590          return false;
591        }
592    
593        // See if the size limit has been exceeded.  If so, then don't send the
594        // entry and indicate that the search should end.
595        if ((getSizeLimit() > 0) && (getEntriesSent() >= getSizeLimit()))
596        {
597          setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED);
598          appendErrorMessage(ERR_SEARCH_SIZE_LIMIT_EXCEEDED.get(getSizeLimit()));
599          return false;
600        }
601    
602        // See if the time limit has expired.  If so, then don't send the entry and
603        // indicate that the search should end.
604        if ((getTimeLimit() > 0) && (TimeThread.getTime() >=
605                                                    getTimeLimitExpiration()))
606        {
607          setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
608          appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit()));
609          return false;
610        }
611    
612        // Determine whether the provided entry is a subentry and if so whether it
613        // should be returned.
614        if ((getScope() != SearchScope.BASE_OBJECT) &&
615            (! isReturnLDAPSubentries()) &&
616            entry.isLDAPSubentry())
617        {
618          // Check to see if the filter contains an equality element with the
619          // objectclass attribute type and a value of "ldapSubentry".  If so, then
620          // we'll return it anyway.  Technically, this isn't part of the
621          // specification so we don't need to get carried away with really in-depth
622          // checks.
623          SearchFilter filter = getFilter();
624          switch (filter.getFilterType())
625          {
626            case AND:
627            case OR:
628              for (SearchFilter f : filter.getFilterComponents())
629              {
630                if ((f.getFilterType() == FilterType.EQUALITY) &&
631                    (f.getAttributeType().isObjectClassType()))
632                {
633                  AttributeValue v = f.getAssertionValue();
634                  if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
635                  {
636                    setReturnLDAPSubentries(true);
637                  }
638                  break;
639                }
640              }
641              break;
642            case EQUALITY:
643              AttributeType t = filter.getAttributeType();
644              if (t.isObjectClassType())
645              {
646                AttributeValue v = filter.getAssertionValue();
647                if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
648                {
649                  setReturnLDAPSubentries(true);
650                }
651              }
652              break;
653          }
654    
655          if (! isReturnLDAPSubentries())
656          {
657            // We still shouldn't return it even based on the filter.  Just throw it
658            // away without doing anything.
659            return true;
660          }
661        }
662    
663    
664        // Determine whether to include the account usable control.  If so, then
665        // create it now.
666        if (isIncludeUsableControl())
667        {
668          try
669          {
670            // FIXME -- Need a way to enable PWP debugging.
671            PasswordPolicyState pwpState = new PasswordPolicyState(entry, false);
672    
673            boolean isInactive           = pwpState.isDisabled() ||
674                                           pwpState.isAccountExpired();
675            boolean isLocked             = pwpState.lockedDueToFailures() ||
676                                           pwpState.lockedDueToMaximumResetAge() ||
677                                           pwpState.lockedDueToIdleInterval();
678            boolean isReset              = pwpState.mustChangePassword();
679            boolean isExpired            = pwpState.isPasswordExpired();
680    
681            if (isInactive || isLocked || isReset || isExpired)
682            {
683              int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
684              int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
685    
686              if (controls == null)
687              {
688                controls = new ArrayList<Control>(1);
689              }
690    
691              controls.add(new AccountUsableResponseControl(isInactive, isReset,
692                                    isExpired, remainingGraceLogins, isLocked,
693                                    secondsBeforeUnlock));
694            }
695            else
696            {
697              if (controls == null)
698              {
699                controls = new ArrayList<Control>(1);
700              }
701    
702              int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
703              controls.add(new AccountUsableResponseControl(
704                                    secondsBeforeExpiration));
705            }
706          }
707          catch (Exception e)
708          {
709            if (debugEnabled())
710            {
711              TRACER.debugCaught(DebugLogLevel.ERROR, e);
712            }
713          }
714        }
715    
716        // Check to see if the entry can be read by the client.
717        SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry,
718            controls);
719        if (AccessControlConfigManager.getInstance()
720            .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) {
721          return true;
722        }
723    
724        // Make a copy of the entry and pare it down to only include the set
725        // of
726        // requested attributes.
727        Entry entryToReturn;
728        if ((getAttributes() == null) || getAttributes().isEmpty())
729        {
730          entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly,
731                                                                      true);
732        }
733        else
734        {
735          entryToReturn = entry.duplicateWithoutAttributes();
736    
737          for (String attrName : getAttributes())
738          {
739            if (attrName.equals("*"))
740            {
741              // This is a special placeholder indicating that all user attributes
742              // should be returned.
743              if (typesOnly)
744              {
745                // First, add the placeholder for the objectclass attribute.
746                AttributeType ocType =
747                     DirectoryServer.getObjectClassAttributeType();
748                List<Attribute> ocList = new ArrayList<Attribute>(1);
749                ocList.add(new Attribute(ocType));
750                entryToReturn.putAttribute(ocType, ocList);
751              }
752              else
753              {
754                // First, add the objectclass attribute.
755                Attribute ocAttr = entry.getObjectClassAttribute();
756                try
757                {
758                  if (ocAttr != null)
759                    entryToReturn.setObjectClasses(ocAttr.getValues());
760                }
761                catch (DirectoryException e)
762                {
763                  // We cannot get this exception because the object classes have
764                  // already been validated in the entry they came from.
765                }
766              }
767    
768              // Next iterate through all the user attributes and include them.
769              for (AttributeType t : entry.getUserAttributes().keySet())
770              {
771                List<Attribute> attrList =
772                     entry.duplicateUserAttribute(t, null, typesOnly);
773                entryToReturn.putAttribute(t, attrList);
774              }
775    
776              continue;
777            }
778            else if (attrName.equals("+"))
779            {
780              // This is a special placeholder indicating that all operational
781              // attributes should be returned.
782              for (AttributeType t : entry.getOperationalAttributes().keySet())
783              {
784                List<Attribute> attrList =
785                     entry.duplicateOperationalAttribute(t, null, typesOnly);
786                entryToReturn.putAttribute(t, attrList);
787              }
788    
789              continue;
790            }
791    
792            String lowerName;
793            HashSet<String> options;
794            int semicolonPos = attrName.indexOf(';');
795            if (semicolonPos > 0)
796            {
797              lowerName = toLowerCase(attrName.substring(0, semicolonPos));
798              int nextPos = attrName.indexOf(';', semicolonPos+1);
799              options = new HashSet<String>();
800              while (nextPos > 0)
801              {
802                options.add(attrName.substring(semicolonPos+1, nextPos));
803    
804                semicolonPos = nextPos;
805                nextPos = attrName.indexOf(';', semicolonPos+1);
806              }
807    
808              options.add(attrName.substring(semicolonPos+1));
809            }
810            else
811            {
812              lowerName = toLowerCase(attrName);
813              options = null;
814            }
815    
816    
817            AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
818            if (attrType == null)
819            {
820              boolean added = false;
821              for (AttributeType t : entry.getUserAttributes().keySet())
822              {
823                if (t.hasNameOrOID(lowerName))
824                {
825                  List<Attribute> attrList =
826                       entry.duplicateUserAttribute(t, options, typesOnly);
827                  if (attrList != null)
828                  {
829                    entryToReturn.putAttribute(t, attrList);
830    
831                    added = true;
832                    break;
833                  }
834                }
835              }
836    
837              if (added)
838              {
839                continue;
840              }
841    
842              for (AttributeType t : entry.getOperationalAttributes().keySet())
843              {
844                if (t.hasNameOrOID(lowerName))
845                {
846                  List<Attribute> attrList =
847                       entry.duplicateOperationalAttribute(t, options, typesOnly);
848                  if (attrList != null)
849                  {
850                    entryToReturn.putAttribute(t, attrList);
851    
852                    break;
853                  }
854                }
855              }
856            }
857            else
858            {
859              if (attrType.isObjectClassType()) {
860                if (typesOnly)
861                {
862                  AttributeType ocType =
863                       DirectoryServer.getObjectClassAttributeType();
864                  List<Attribute> ocList = new ArrayList<Attribute>(1);
865                  ocList.add(new Attribute(ocType));
866                  entryToReturn.putAttribute(ocType, ocList);
867                }
868                else
869                {
870                  List<Attribute> attrList = new ArrayList<Attribute>(1);
871                  attrList.add(entry.getObjectClassAttribute());
872                  entryToReturn.putAttribute(attrType, attrList);
873                }
874              }
875              else
876              {
877                List<Attribute> attrList =
878                     entry.duplicateOperationalAttribute(attrType, options,
879                                                         typesOnly);
880                if (attrList == null)
881                {
882                  attrList = entry.duplicateUserAttribute(attrType, options,
883                                                          typesOnly);
884                }
885                if (attrList != null)
886                {
887                  entryToReturn.putAttribute(attrType, attrList);
888                }
889              }
890            }
891          }
892        }
893    
894        if (isRealAttributesOnly())
895        {
896          entryToReturn.stripVirtualAttributes();
897        }
898        else if (isVirtualAttributesOnly())
899        {
900          entryToReturn.stripRealAttributes();
901        }
902    
903        // If there is a matched values control, then further pare down the entry
904        // based on the filters that it contains.
905        MatchedValuesControl matchedValuesControl = getMatchedValuesControl();
906        if ((matchedValuesControl != null) && (! typesOnly))
907        {
908          // First, look at the set of objectclasses.
909          AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
910          Iterator<String> ocIterator =
911               entryToReturn.getObjectClasses().values().iterator();
912          while (ocIterator.hasNext())
913          {
914            String ocName = ocIterator.next();
915            AttributeValue v = new AttributeValue(attrType,
916                                                  new ASN1OctetString(ocName));
917            if (! matchedValuesControl.valueMatches(attrType, v))
918            {
919              ocIterator.remove();
920            }
921          }
922    
923    
924          // Next, the set of user attributes.
925          for (AttributeType t : entryToReturn.getUserAttributes().keySet())
926          {
927            for (Attribute a : entryToReturn.getUserAttribute(t))
928            {
929              Iterator<AttributeValue> valueIterator = a.getValues().iterator();
930              while (valueIterator.hasNext())
931              {
932                AttributeValue v = valueIterator.next();
933                if (! matchedValuesControl.valueMatches(t, v))
934                {
935                  valueIterator.remove();
936                }
937              }
938            }
939          }
940    
941    
942          // Then the set of operational attributes.
943          for (AttributeType t : entryToReturn.getOperationalAttributes().keySet())
944          {
945            for (Attribute a : entryToReturn.getOperationalAttribute(t))
946            {
947              Iterator<AttributeValue> valueIterator = a.getValues().iterator();
948              while (valueIterator.hasNext())
949              {
950                AttributeValue v = valueIterator.next();
951                if (! matchedValuesControl.valueMatches(t, v))
952                {
953                  valueIterator.remove();
954                }
955              }
956            }
957          }
958        }
959    
960    
961        // Convert the provided entry to a search result entry.
962        SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn,
963                                                              controls);
964    
965        // Strip out any attributes that the client does not have access to.
966    
967        // FIXME: need some way to prevent plugins from adding attributes or
968        // values that the client is not permitted to see.
969        searchEntry = AccessControlConfigManager.getInstance()
970            .getAccessControlHandler().filterEntry(this, searchEntry);
971    
972        // Invoke any search entry plugins that may be registered with the server.
973        PluginResult.IntermediateResponse pluginResult =
974             DirectoryServer.getPluginConfigManager().
975                  invokeSearchResultEntryPlugins(this, searchEntry);
976    
977        // Send the entry to the client.
978        if (pluginResult.sendResponse())
979        {
980          try
981          {
982            sendSearchEntry(searchEntry);
983            // Log the entry sent to the client.
984            logSearchResultEntry(this, searchEntry);
985    
986            incrementEntriesSent();
987          }
988          catch (DirectoryException de)
989          {
990            if (debugEnabled())
991            {
992              TRACER.debugCaught(DebugLogLevel.ERROR, de);
993            }
994    
995            setResponseData(de);
996            return false;
997          }
998        }
999    
1000        return pluginResult.continueProcessing();
1001      }
1002    
1003      /**
1004       * {@inheritDoc}
1005       */
1006      public final boolean returnReference(DN dn, SearchResultReference reference)
1007      {
1008        // See if the operation has been abandoned.  If so, then don't send the
1009        // reference and indicate that the search should end.
1010        if (getCancelRequest() != null)
1011        {
1012          setResultCode(ResultCode.CANCELED);
1013          return false;
1014        }
1015    
1016    
1017        // See if the time limit has expired.  If so, then don't send the entry and
1018        // indicate that the search should end.
1019        if ((getTimeLimit() > 0) && (TimeThread.getTime() >=
1020                                                    getTimeLimitExpiration()))
1021        {
1022          setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
1023          appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit()));
1024          return false;
1025        }
1026    
1027    
1028        // See if we know that this client can't handle referrals.  If so, then
1029        // don't even try to send it.
1030        if (! isClientAcceptsReferrals())
1031        {
1032          return true;
1033        }
1034    
1035    
1036        // See if the client has permission to read this reference.
1037        if (AccessControlConfigManager.getInstance()
1038            .getAccessControlHandler().maySend(dn, this, reference) == false) {
1039          return true;
1040        }
1041    
1042    
1043        // Invoke any search reference plugins that may be registered with the
1044        // server.
1045        PluginResult.IntermediateResponse pluginResult =
1046             DirectoryServer.getPluginConfigManager().
1047                  invokeSearchResultReferencePlugins(this, reference);
1048    
1049        // Send the reference to the client.  Note that this could throw an
1050        // exception, which would indicate that the associated client can't handle
1051        // referrals.  If that't the case, then set a flag so we'll know not to try
1052        // to send any more.
1053        if (pluginResult.sendResponse())
1054        {
1055          try
1056          {
1057            if (sendSearchReference(reference))
1058            {
1059              // Log the entry sent to the client.
1060              logSearchResultReference(this, reference);
1061              incrementReferencesSent();
1062    
1063              // FIXME -- Should the size limit apply here?
1064            }
1065            else
1066            {
1067              // We know that the client can't handle referrals, so we won't try to
1068              // send it any more.
1069              setClientAcceptsReferrals(false);
1070            }
1071          }
1072          catch (DirectoryException de)
1073          {
1074            if (debugEnabled())
1075            {
1076              TRACER.debugCaught(DebugLogLevel.ERROR, de);
1077            }
1078    
1079            setResponseData(de);
1080            return false;
1081          }
1082        }
1083    
1084        return pluginResult.continueProcessing();
1085      }
1086    
1087      /**
1088       * {@inheritDoc}
1089       */
1090      public final void sendSearchResultDone()
1091      {
1092        // Send the search result done message to the client.  We want to make sure
1093        // that this only gets sent once, and it's possible that this could be
1094        // multithreaded in the event of a persistent search, so do it safely.
1095        if (responseSent.compareAndSet(false, true))
1096        {
1097          // Send the response to the client.
1098          clientConnection.sendResponse(this);
1099    
1100          // Log the search result.
1101          logSearchResultDone(this);
1102    
1103    
1104          // Invoke the post-response search plugins.
1105          invokePostResponsePlugins();
1106        }
1107      }
1108    
1109      /**
1110       * {@inheritDoc}
1111       */
1112      @Override()
1113      public final OperationType getOperationType()
1114      {
1115        // Note that no debugging will be done in this method because it is a likely
1116        // candidate for being called by the logging subsystem.
1117    
1118        return OperationType.SEARCH;
1119      }
1120    
1121      /**
1122       * {@inheritDoc}
1123       */
1124      @Override()
1125      public final String[][] getRequestLogElements()
1126      {
1127        // Note that no debugging will be done in this method because it is a likely
1128        // candidate for being called by the logging subsystem.
1129    
1130        String attrs;
1131        if ((attributes == null) || attributes.isEmpty())
1132        {
1133          attrs = null;
1134        }
1135        else
1136        {
1137          StringBuilder attrBuffer = new StringBuilder();
1138          Iterator<String> iterator = attributes.iterator();
1139          attrBuffer.append(iterator.next());
1140    
1141          while (iterator.hasNext())
1142          {
1143            attrBuffer.append(", ");
1144            attrBuffer.append(iterator.next());
1145          }
1146    
1147          attrs = attrBuffer.toString();
1148        }
1149    
1150        return new String[][]
1151        {
1152          new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) },
1153          new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) },
1154          new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) },
1155          new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) },
1156          new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) },
1157          new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs }
1158        };
1159      }
1160    
1161      /**
1162       * {@inheritDoc}
1163       */
1164      @Override()
1165      public final String[][] getResponseLogElements()
1166      {
1167        // Note that no debugging will be done in this method because it is a likely
1168        // candidate for being called by the logging subsystem.
1169    
1170        String resultCode = String.valueOf(getResultCode().getIntValue());
1171    
1172        String errorMessage;
1173        MessageBuilder errorMessageBuffer = getErrorMessage();
1174        if (errorMessageBuffer == null)
1175        {
1176          errorMessage = null;
1177        }
1178        else
1179        {
1180          errorMessage = errorMessageBuffer.toString();
1181        }
1182    
1183        String matchedDNStr;
1184        DN matchedDN = getMatchedDN();
1185        if (matchedDN == null)
1186        {
1187          matchedDNStr = null;
1188        }
1189        else
1190        {
1191          matchedDNStr = matchedDN.toString();
1192        }
1193    
1194        String referrals;
1195        List<String> referralURLs = getReferralURLs();
1196        if ((referralURLs == null) || referralURLs.isEmpty())
1197        {
1198          referrals = null;
1199        }
1200        else
1201        {
1202          StringBuilder buffer = new StringBuilder();
1203          Iterator<String> iterator = referralURLs.iterator();
1204          buffer.append(iterator.next());
1205    
1206          while (iterator.hasNext())
1207          {
1208            buffer.append(", ");
1209            buffer.append(iterator.next());
1210          }
1211    
1212          referrals = buffer.toString();
1213        }
1214    
1215        String processingTime =
1216             String.valueOf(processingStopTime - processingStartTime);
1217    
1218        return new String[][]
1219        {
1220          new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
1221          new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
1222          new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
1223          new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
1224          new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) },
1225          new String[] { LOG_ELEMENT_REFERENCES_SENT,
1226                         String.valueOf(referencesSent ) },
1227          new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
1228        };
1229      }
1230    
1231      /**
1232       * {@inheritDoc}
1233       */
1234      public DN getProxiedAuthorizationDN()
1235      {
1236        return proxiedAuthorizationDN;
1237      }
1238    
1239      /**
1240       * {@inheritDoc}
1241       */
1242      @Override()
1243      public final List<Control> getResponseControls()
1244      {
1245        return responseControls;
1246      }
1247    
1248      /**
1249       * {@inheritDoc}
1250       */
1251      @Override()
1252      public final void addResponseControl(Control control)
1253      {
1254        responseControls.add(control);
1255      }
1256    
1257      /**
1258       * {@inheritDoc}
1259       */
1260      @Override()
1261      public final void removeResponseControl(Control control)
1262      {
1263        responseControls.remove(control);
1264      }
1265    
1266    
1267    
1268      /**
1269       * {@inheritDoc}
1270       */
1271      @Override()
1272      public void abort(CancelRequest cancelRequest)
1273      {
1274        if(cancelResult == null && this.cancelRequest == null)
1275        {
1276          this.cancelRequest = cancelRequest;
1277    
1278          if (persistentSearch != null)
1279          {
1280            DirectoryServer.deregisterPersistentSearch(persistentSearch);
1281            persistentSearch = null;
1282          }
1283        }
1284      }
1285    
1286    
1287    
1288      /**
1289       * {@inheritDoc}
1290       */
1291      @Override()
1292      public final void toString(StringBuilder buffer)
1293      {
1294        buffer.append("SearchOperation(connID=");
1295        buffer.append(clientConnection.getConnectionID());
1296        buffer.append(", opID=");
1297        buffer.append(operationID);
1298        buffer.append(", baseDN=");
1299        buffer.append(rawBaseDN);
1300        buffer.append(", scope=");
1301        buffer.append(scope.toString());
1302        buffer.append(", filter=");
1303        buffer.append(rawFilter.toString());
1304        buffer.append(")");
1305      }
1306    
1307      /**
1308       * {@inheritDoc}
1309       */
1310      public void setTimeLimitExpiration(Long timeLimitExpiration){
1311        this.timeLimitExpiration = timeLimitExpiration;
1312      }
1313    
1314      /**
1315       * {@inheritDoc}
1316       */
1317      public boolean isReturnLDAPSubentries()
1318      {
1319        return returnLDAPSubentries;
1320      }
1321    
1322      /**
1323       * {@inheritDoc}
1324       */
1325      public void setReturnLDAPSubentries(boolean returnLDAPSubentries)
1326      {
1327        this.returnLDAPSubentries = returnLDAPSubentries;
1328      }
1329    
1330      /**
1331       * {@inheritDoc}
1332       */
1333      public MatchedValuesControl getMatchedValuesControl()
1334      {
1335        return matchedValuesControl;
1336      }
1337    
1338      /**
1339       * {@inheritDoc}
1340       */
1341      public void setMatchedValuesControl(MatchedValuesControl controls)
1342      {
1343        this.matchedValuesControl = controls;
1344      }
1345    
1346      /**
1347       * {@inheritDoc}
1348       */
1349      public PersistentSearch getPersistentSearch()
1350      {
1351        return persistentSearch;
1352      }
1353    
1354      /**
1355       * {@inheritDoc}
1356       */
1357      public boolean isIncludeUsableControl()
1358      {
1359        return includeUsableControl;
1360      }
1361    
1362      /**
1363       * {@inheritDoc}
1364       */
1365      public void setIncludeUsableControl(boolean includeUsableControl)
1366      {
1367        this.includeUsableControl = includeUsableControl;
1368      }
1369    
1370      /**
1371       * {@inheritDoc}
1372       */
1373      public void setPersistentSearch(PersistentSearch psearch)
1374      {
1375        this.persistentSearch = psearch;
1376      }
1377    
1378      /**
1379       * {@inheritDoc}
1380       */
1381      public Long getTimeLimitExpiration()
1382      {
1383        return timeLimitExpiration;
1384      }
1385    
1386      /**
1387       * {@inheritDoc}
1388       */
1389      public boolean isClientAcceptsReferrals()
1390      {
1391        return clientAcceptsReferrals;
1392      }
1393    
1394      /**
1395       * {@inheritDoc}
1396       */
1397      public void setClientAcceptsReferrals(boolean clientAcceptReferrals)
1398      {
1399        this.clientAcceptsReferrals = clientAcceptReferrals;
1400      }
1401    
1402      /**
1403       * {@inheritDoc}
1404       */
1405      public void incrementEntriesSent()
1406      {
1407        entriesSent++;
1408      }
1409    
1410      /**
1411       * {@inheritDoc}
1412       */
1413      public void incrementReferencesSent()
1414      {
1415        referencesSent++;
1416      }
1417    
1418      /**
1419       * {@inheritDoc}
1420       */
1421      public boolean isSendResponse()
1422      {
1423        return sendResponse;
1424      }
1425    
1426      /**
1427       * {@inheritDoc}
1428       */
1429      public void setSendResponse(boolean sendResponse)
1430      {
1431        this.sendResponse = sendResponse;
1432    
1433      }
1434    
1435      /**
1436       * {@inheritDoc}
1437       */
1438      public boolean isRealAttributesOnly()
1439      {
1440        return this.realAttributesOnly;
1441      }
1442    
1443      /**
1444       * {@inheritDoc}
1445       */
1446      public boolean isVirtualAttributesOnly()
1447      {
1448        return this.virtualAttributesOnly;
1449      }
1450    
1451      /**
1452       * {@inheritDoc}
1453       */
1454      public void setRealAttributesOnly(boolean realAttributesOnly)
1455      {
1456        this.realAttributesOnly = realAttributesOnly;
1457      }
1458    
1459      /**
1460       * {@inheritDoc}
1461       */
1462      public void setVirtualAttributesOnly(boolean virtualAttributesOnly)
1463      {
1464        this.virtualAttributesOnly = virtualAttributesOnly;
1465      }
1466    
1467      /**
1468       * {@inheritDoc}
1469       */
1470      public void sendSearchEntry(SearchResultEntry searchEntry)
1471        throws DirectoryException
1472        {
1473        getClientConnection().sendSearchEntry(this, searchEntry);
1474      }
1475    
1476      /**
1477       * {@inheritDoc}
1478       */
1479      public boolean sendSearchReference(SearchResultReference searchReference)
1480      throws DirectoryException
1481      {
1482        return getClientConnection().sendSearchReference(this, searchReference);
1483      }
1484    
1485      /**
1486       * {@inheritDoc}
1487       */
1488      public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
1489      {
1490        this.proxiedAuthorizationDN = proxiedAuthorizationDN;
1491      }
1492    
1493      /**
1494       * {@inheritDoc}
1495       */
1496      public final void run()
1497      {
1498        setResultCode(ResultCode.UNDEFINED);
1499    
1500        // Start the processing timer.
1501        setProcessingStartTime();
1502    
1503        // Log the search request message.
1504        logSearchRequest(this);
1505    
1506        setSendResponse(true);
1507    
1508        // Get the plugin config manager that will be used for invoking plugins.
1509        PluginConfigManager pluginConfigManager =
1510            DirectoryServer.getPluginConfigManager();
1511    
1512        int timeLimit = getTimeLimit();
1513        Long timeLimitExpiration;
1514        if (timeLimit <= 0)
1515        {
1516          timeLimitExpiration = Long.MAX_VALUE;
1517        }
1518        else
1519        {
1520          // FIXME -- Factor in the user's effective time limit.
1521          timeLimitExpiration =
1522              getProcessingStartTime() + (1000L * timeLimit);
1523        }
1524        setTimeLimitExpiration(timeLimitExpiration);
1525    
1526        try
1527        {
1528          // Check for and handle a request to cancel this operation.
1529          checkIfCanceled(false);
1530    
1531          PluginResult.PreParse preParseResult =
1532              pluginConfigManager.invokePreParseSearchPlugins(this);
1533    
1534          if(!preParseResult.continueProcessing())
1535          {
1536            setResultCode(preParseResult.getResultCode());
1537            appendErrorMessage(preParseResult.getErrorMessage());
1538            setMatchedDN(preParseResult.getMatchedDN());
1539            setReferralURLs(preParseResult.getReferralURLs());
1540            return;
1541          }
1542    
1543          // Check for and handle a request to cancel this operation.
1544          checkIfCanceled(false);
1545    
1546          // Process the search base and filter to convert them from their raw forms
1547          // as provided by the client to the forms required for the rest of the
1548          // search processing.
1549          DN baseDN = getBaseDN();
1550          if (baseDN == null){
1551            return;
1552          }
1553    
1554    
1555          // Retrieve the network group attached to the client connection
1556          // and get a workflow to process the operation.
1557          NetworkGroup ng = getClientConnection().getNetworkGroup();
1558          Workflow workflow = ng.getWorkflowCandidate(baseDN);
1559          if (workflow == null)
1560          {
1561            // We have found no workflow for the requested base DN, just return
1562            // a no such entry result code and stop the processing.
1563            updateOperationErrMsgAndResCode();
1564            return;
1565          }
1566          workflow.execute(this);
1567        }
1568        catch(CanceledOperationException coe)
1569        {
1570          if (debugEnabled())
1571          {
1572            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
1573          }
1574    
1575          setResultCode(ResultCode.CANCELED);
1576          cancelResult = new CancelResult(ResultCode.CANCELED, null);
1577    
1578          appendErrorMessage(coe.getCancelRequest().getCancelReason());
1579        }
1580        finally
1581        {
1582          // Stop the processing timer.
1583          setProcessingStopTime();
1584    
1585          if(cancelRequest == null || cancelResult == null ||
1586              cancelResult.getResultCode() != ResultCode.CANCELED)
1587          {
1588            // If everything is successful to this point and it is not a persistent
1589            // search, then send the search result done message to the client.
1590            // Otherwise, we'll want to make the size and time limit values
1591            // unlimited to ensure that the remainder of the persistent search
1592            // isn't subject to those restrictions.
1593            if (isSendResponse())
1594            {
1595              sendSearchResultDone();
1596            }
1597            else
1598            {
1599              setSizeLimit(0);
1600              setTimeLimit(0);
1601            }
1602          }
1603          else if(cancelRequest.notifyOriginalRequestor() ||
1604              DirectoryServer.notifyAbandonedOperations())
1605          {
1606            sendSearchResultDone();
1607          }
1608    
1609          // If no cancel result, set it
1610          if(cancelResult == null)
1611          {
1612            cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
1613          }
1614        }
1615      }
1616    
1617    
1618      /**
1619       * Invokes the post response plugins.
1620       */
1621      private void invokePostResponsePlugins()
1622      {
1623        // Get the plugin config manager that will be used for invoking plugins.
1624        PluginConfigManager pluginConfigManager =
1625          DirectoryServer.getPluginConfigManager();
1626    
1627        // Invoke the post response plugins that have been registered with
1628        // the current operation
1629        pluginConfigManager.invokePostResponseSearchPlugins(this);
1630      }
1631    
1632    
1633      /**
1634       * Updates the error message and the result code of the operation.
1635       *
1636       * This method is called because no workflows were found to process
1637       * the operation.
1638       */
1639      private void updateOperationErrMsgAndResCode()
1640      {
1641        setResultCode(ResultCode.NO_SUCH_OBJECT);
1642        Message message =
1643                ERR_SEARCH_BASE_DOESNT_EXIST.get(String.valueOf(getBaseDN()));
1644        appendErrorMessage(message);
1645      }
1646    
1647    }