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 }