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    
030    import java.util.ArrayList;
031    import java.util.Set;
032    
033    import org.opends.server.controls.EntryChangeNotificationControl;
034    import org.opends.server.controls.PersistentSearchChangeType;
035    import org.opends.server.types.Control;
036    import org.opends.server.types.DN;
037    import org.opends.server.types.DebugLogLevel;
038    import org.opends.server.types.DirectoryException;
039    import org.opends.server.types.Entry;
040    import org.opends.server.types.SearchFilter;
041    import org.opends.server.types.SearchScope;
042    import org.opends.server.workflowelement.localbackend.*;
043    
044    import static org.opends.server.loggers.debug.DebugLogger.*;
045    import org.opends.server.loggers.debug.DebugTracer;
046    
047    
048    
049    /**
050     * This class defines a data structure that will be used to hold the information
051     * necessary for processing a persistent search.
052     */
053    public class PersistentSearch
054    {
055      /**
056       * The tracer object for the debug logger.
057       */
058      private static final DebugTracer TRACER = getTracer();
059    
060      // Indicates whether entries returned should include the entry change
061      // notification control.
062      private boolean returnECs;
063    
064      // The base DN for the search operation.
065      private DN baseDN;
066    
067      // The set of change types we want to see.
068      private Set<PersistentSearchChangeType> changeTypes;
069    
070      // The scope for the search operation.
071      private SearchScope scope;
072    
073      // The filter for the search operation.
074      private SearchFilter filter;
075    
076      // The reference to the associated search operation.
077      private SearchOperation searchOperation;
078    
079    
080    
081      /**
082       * Creates a new persistent search object with the provided information.
083       *
084       * @param  searchOperation  The search operation for this persistent search.
085       * @param  changeTypes      The change types for which changes should be
086       *                          examined.
087       * @param  returnECs        Indicates whether to include entry change
088       *                          notification controls in search result entries
089       *                          sent to the client.
090       */
091      public PersistentSearch(SearchOperation searchOperation,
092                              Set<PersistentSearchChangeType> changeTypes,
093                              boolean returnECs)
094      {
095        this.searchOperation = searchOperation;
096        this.changeTypes     = changeTypes;
097        this.returnECs       = returnECs;
098    
099        baseDN = searchOperation.getBaseDN();
100        scope  = searchOperation.getScope();
101        filter = searchOperation.getFilter();
102      }
103    
104    
105    
106      /**
107       * Retrieves the search operation for this persistent search.
108       *
109       * @return  The search operation for this persistent search.
110       */
111      public SearchOperation getSearchOperation()
112      {
113        return searchOperation;
114      }
115    
116    
117    
118      /**
119       * Retrieves the set of change types for this persistent search.
120       *
121       * @return  The set of change types for this persistent search.
122       */
123      public Set<PersistentSearchChangeType> getChangeTypes()
124      {
125        return changeTypes;
126      }
127    
128    
129    
130      /**
131       * Retrieves the returnECs flag for this persistent search.
132       *
133       * @return  The return ECs flag for this persistent search.
134       */
135      public boolean getReturnECs()
136      {
137        return returnECs;
138      }
139    
140    
141    
142      /**
143       * Retrieves the base DN for this persistent search.
144       *
145       * @return  The base DN for this persistent search.
146       */
147      public DN getBaseDN()
148      {
149        return baseDN;
150      }
151    
152    
153    
154      /**
155       * Retrieves the scope for this persistent search.
156       *
157       * @return  The scope for this persistent search.
158       */
159      public SearchScope getScope()
160      {
161        return scope;
162      }
163    
164    
165    
166      /**
167       * Retrieves the filter for this persistent search.
168       *
169       * @return  The filter for this persistent search.
170       */
171      public SearchFilter getFilter()
172      {
173        return filter;
174      }
175    
176    
177    
178      /**
179       * Performs any necessary processing for the provided add operation.
180       *
181       * @param  addOperation  The add operation that has been processed.
182       * @param  entry         The entry that was added.
183       */
184      public void processAdd(LocalBackendAddOperation addOperation, Entry entry)
185      {
186        // See if we care about add operations.
187        if (! changeTypes.contains(PersistentSearchChangeType.ADD))
188        {
189          return;
190        }
191    
192    
193        // Make sure that the entry is within our target scope.
194        switch (scope)
195        {
196          case BASE_OBJECT:
197            if (! baseDN.equals(entry.getDN()))
198            {
199              return;
200            }
201            break;
202          case SINGLE_LEVEL:
203            if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
204            {
205              return;
206            }
207            break;
208          case WHOLE_SUBTREE:
209            if (! baseDN.isAncestorOf(entry.getDN()))
210            {
211              return;
212            }
213            break;
214          case SUBORDINATE_SUBTREE:
215            if (baseDN.equals(entry.getDN()) ||
216                (! baseDN.isAncestorOf(entry.getDN())))
217            {
218              return;
219            }
220            break;
221          default:
222            return;
223        }
224    
225    
226        // Make sure that the entry matches the target filter.
227        try
228        {
229          if (! filter.matchesEntry(entry))
230          {
231            return;
232          }
233        }
234        catch (DirectoryException de)
235        {
236          if (debugEnabled())
237          {
238            TRACER.debugCaught(DebugLogLevel.ERROR, de);
239          }
240    
241          // FIXME -- Do we need to do anything here?
242    
243          return;
244        }
245    
246    
247        // The entry is one that should be sent to the client.  See if we also need
248        // to construct an entry change notification control.
249        ArrayList<Control> entryControls = new ArrayList<Control>(1);
250        if (returnECs)
251        {
252          entryControls.add(new EntryChangeNotificationControl(
253                                     PersistentSearchChangeType.ADD,
254                                     addOperation.getChangeNumber()));
255        }
256    
257    
258        // Send the entry and see if we should continue processing.  If not, then
259        // deregister this persistent search.
260        try
261        {
262          if (! searchOperation.returnEntry(entry, entryControls))
263          {
264            DirectoryServer.deregisterPersistentSearch(this);
265            searchOperation.sendSearchResultDone();
266          }
267        }
268        catch (Exception e)
269        {
270          if (debugEnabled())
271          {
272            TRACER.debugCaught(DebugLogLevel.ERROR, e);
273          }
274    
275          DirectoryServer.deregisterPersistentSearch(this);
276    
277          try
278          {
279            searchOperation.sendSearchResultDone();
280          }
281          catch (Exception e2)
282          {
283            if (debugEnabled())
284            {
285              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
286            }
287          }
288        }
289      }
290    
291    
292    
293      /**
294       * Performs any necessary processing for the provided delete operation.
295       *
296       * @param  deleteOperation  The delete operation that has been processed.
297       * @param  entry            The entry that was removed.
298       */
299      public void processDelete(LocalBackendDeleteOperation deleteOperation,
300          Entry entry)
301      {
302        // See if we care about delete operations.
303        if (! changeTypes.contains(PersistentSearchChangeType.DELETE))
304        {
305          return;
306        }
307    
308    
309        // Make sure that the entry is within our target scope.
310        switch (scope)
311        {
312          case BASE_OBJECT:
313            if (! baseDN.equals(entry.getDN()))
314            {
315              return;
316            }
317            break;
318          case SINGLE_LEVEL:
319            if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
320            {
321              return;
322            }
323            break;
324          case WHOLE_SUBTREE:
325            if (! baseDN.isAncestorOf(entry.getDN()))
326            {
327              return;
328            }
329            break;
330          case SUBORDINATE_SUBTREE:
331            if (baseDN.equals(entry.getDN()) ||
332                (! baseDN.isAncestorOf(entry.getDN())))
333            {
334              return;
335            }
336            break;
337          default:
338            return;
339        }
340    
341    
342        // Make sure that the entry matches the target filter.
343        try
344        {
345          if (! filter.matchesEntry(entry))
346          {
347            return;
348          }
349        }
350        catch (DirectoryException de)
351        {
352          if (debugEnabled())
353          {
354            TRACER.debugCaught(DebugLogLevel.ERROR, de);
355          }
356    
357          // FIXME -- Do we need to do anything here?
358    
359          return;
360        }
361    
362    
363        // The entry is one that should be sent to the client.  See if we also need
364        // to construct an entry change notification control.
365        ArrayList<Control> entryControls = new ArrayList<Control>(1);
366        if (returnECs)
367        {
368          entryControls.add(new EntryChangeNotificationControl(
369                                     PersistentSearchChangeType.DELETE,
370                                     deleteOperation.getChangeNumber()));
371        }
372    
373    
374        // Send the entry and see if we should continue processing.  If not, then
375        // deregister this persistent search.
376        try
377        {
378          if (! searchOperation.returnEntry(entry, entryControls))
379          {
380            DirectoryServer.deregisterPersistentSearch(this);
381            searchOperation.sendSearchResultDone();
382          }
383        }
384        catch (Exception e)
385        {
386          if (debugEnabled())
387          {
388            TRACER.debugCaught(DebugLogLevel.ERROR, e);
389          }
390    
391          DirectoryServer.deregisterPersistentSearch(this);
392    
393          try
394          {
395            searchOperation.sendSearchResultDone();
396          }
397          catch (Exception e2)
398          {
399            if (debugEnabled())
400            {
401              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
402            }
403          }
404        }
405      }
406    
407    
408    
409      /**
410       * Performs any necessary processing for the provided modify operation.
411       *
412       * @param  modifyOperation  The modify operation that has been processed.
413       * @param  oldEntry         The entry before the modification was applied.
414       * @param  newEntry         The entry after the modification was applied.
415       */
416      public void processModify(LocalBackendModifyOperation modifyOperation,
417                                Entry oldEntry,
418                                Entry newEntry)
419      {
420        // See if we care about modify operations.
421        if (! changeTypes.contains(PersistentSearchChangeType.MODIFY))
422        {
423          return;
424        }
425    
426    
427        // Make sure that the entry is within our target scope.
428        switch (scope)
429        {
430          case BASE_OBJECT:
431            if (! baseDN.equals(oldEntry.getDN()))
432            {
433              return;
434            }
435            break;
436          case SINGLE_LEVEL:
437            if (! baseDN.equals(oldEntry.getDN().getParentDNInSuffix()))
438            {
439              return;
440            }
441            break;
442          case WHOLE_SUBTREE:
443            if (! baseDN.isAncestorOf(oldEntry.getDN()))
444            {
445              return;
446            }
447            break;
448          case SUBORDINATE_SUBTREE:
449            if (baseDN.equals(oldEntry.getDN()) ||
450                (! baseDN.isAncestorOf(oldEntry.getDN())))
451            {
452              return;
453            }
454            break;
455          default:
456            return;
457        }
458    
459    
460        // Make sure that the entry matches the target filter.
461        try
462        {
463          if ((! filter.matchesEntry(oldEntry)) &&
464              (! filter.matchesEntry(newEntry)))
465          {
466            return;
467          }
468        }
469        catch (DirectoryException de)
470        {
471          if (debugEnabled())
472          {
473            TRACER.debugCaught(DebugLogLevel.ERROR, de);
474          }
475    
476          // FIXME -- Do we need to do anything here?
477    
478          return;
479        }
480    
481    
482        // The entry is one that should be sent to the client.  See if we also need
483        // to construct an entry change notification control.
484        ArrayList<Control> entryControls = new ArrayList<Control>(1);
485        if (returnECs)
486        {
487          entryControls.add(new EntryChangeNotificationControl(
488                                     PersistentSearchChangeType.MODIFY,
489                                     modifyOperation.getChangeNumber()));
490        }
491    
492    
493        // Send the entry and see if we should continue processing.  If not, then
494        // deregister this persistent search.
495        try
496        {
497          if (! searchOperation.returnEntry(newEntry, entryControls))
498          {
499            DirectoryServer.deregisterPersistentSearch(this);
500            searchOperation.sendSearchResultDone();
501          }
502        }
503        catch (Exception e)
504        {
505          if (debugEnabled())
506          {
507            TRACER.debugCaught(DebugLogLevel.ERROR, e);
508          }
509    
510          DirectoryServer.deregisterPersistentSearch(this);
511    
512          try
513          {
514            searchOperation.sendSearchResultDone();
515          }
516          catch (Exception e2)
517          {
518            if (debugEnabled())
519            {
520              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
521            }
522          }
523        }
524      }
525    
526    
527    
528      /**
529       * Performs any necessary processing for the provided modify DN operation.
530       *
531       * @param  modifyDNOperation  The modify DN operation that has been processed.
532       * @param  oldEntry           The entry before the modify DN.
533       * @param  newEntry           The entry after the modify DN.
534       */
535      public void processModifyDN(LocalBackendModifyDNOperation modifyDNOperation,
536                                  Entry oldEntry, Entry newEntry)
537      {
538        // See if we care about modify DN operations.
539        if (! changeTypes.contains(PersistentSearchChangeType.MODIFY_DN))
540        {
541          return;
542        }
543    
544    
545        // Make sure that the old or new entry is within our target scope.  In this
546        // case, we need to check the DNs of both the old and new entry so we know
547        // which one(s) should be compared against the filter.
548        boolean oldMatches = false;
549        boolean newMatches = false;
550    
551        switch (scope)
552        {
553          case BASE_OBJECT:
554            oldMatches = baseDN.equals(oldEntry.getDN());
555            newMatches = baseDN.equals(newEntry.getDN());
556    
557            if (! (oldMatches || newMatches))
558            {
559              return;
560            }
561    
562            break;
563          case SINGLE_LEVEL:
564            oldMatches = baseDN.equals(oldEntry.getDN().getParentDNInSuffix());
565            newMatches = baseDN.equals(newEntry.getDN().getParentDNInSuffix());
566    
567            if (! (oldMatches || newMatches))
568            {
569              return;
570            }
571    
572            break;
573          case WHOLE_SUBTREE:
574            oldMatches = baseDN.isAncestorOf(oldEntry.getDN());
575            newMatches = baseDN.isAncestorOf(newEntry.getDN());
576    
577            if (! (oldMatches || newMatches))
578            {
579              return;
580            }
581    
582            break;
583          case SUBORDINATE_SUBTREE:
584            oldMatches = ((! baseDN.equals(oldEntry.getDN())) &&
585                          baseDN.isAncestorOf(oldEntry.getDN()));
586            newMatches = ((! baseDN.equals(newEntry.getDN())) &&
587                          baseDN.isAncestorOf(newEntry.getDN()));
588    
589            if (! (oldMatches || newMatches))
590            {
591              return;
592            }
593    
594            break;
595          default:
596            return;
597        }
598    
599    
600        // Make sure that the entry matches the target filter.
601        try
602        {
603          if (((! oldMatches) || (! filter.matchesEntry(oldEntry))) &&
604              ((! newMatches) && (! filter.matchesEntry(newEntry))))
605          {
606            return;
607          }
608        }
609        catch (DirectoryException de)
610        {
611          if (debugEnabled())
612          {
613            TRACER.debugCaught(DebugLogLevel.ERROR, de);
614          }
615    
616          // FIXME -- Do we need to do anything here?
617    
618          return;
619        }
620    
621    
622        // The entry is one that should be sent to the client.  See if we also need
623        // to construct an entry change notification control.
624        ArrayList<Control> entryControls = new ArrayList<Control>(1);
625        if (returnECs)
626        {
627          entryControls.add(new EntryChangeNotificationControl(
628                                     PersistentSearchChangeType.MODIFY_DN,
629                                     oldEntry.getDN(),
630                                     modifyDNOperation.getChangeNumber()));
631        }
632    
633    
634        // Send the entry and see if we should continue processing.  If not, then
635        // deregister this persistent search.
636        try
637        {
638          if (! searchOperation.returnEntry(newEntry, entryControls))
639          {
640            DirectoryServer.deregisterPersistentSearch(this);
641            searchOperation.sendSearchResultDone();
642          }
643        }
644        catch (Exception e)
645        {
646          if (debugEnabled())
647          {
648            TRACER.debugCaught(DebugLogLevel.ERROR, e);
649          }
650    
651          DirectoryServer.deregisterPersistentSearch(this);
652    
653          try
654          {
655            searchOperation.sendSearchResultDone();
656          }
657          catch (Exception e2)
658          {
659            if (debugEnabled())
660            {
661              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
662            }
663          }
664        }
665      }
666    
667    
668    
669      /**
670       * Retrieves a string representation of this persistent search.
671       *
672       * @return  A string representation of this persistent search.
673       */
674      public String toString()
675      {
676        StringBuilder buffer = new StringBuilder();
677        toString(buffer);
678        return buffer.toString();
679      }
680    
681    
682    
683      /**
684       * Appends a string representation of this persistent search to the provided
685       * buffer.
686       *
687       * @param  buffer  The buffer to which the information should be appended.
688       */
689      public void toString(StringBuilder buffer)
690      {
691        buffer.append("PersistentSearch(connID=");
692        buffer.append(searchOperation.getConnectionID());
693        buffer.append(",opID=");
694        buffer.append(searchOperation.getOperationID());
695        buffer.append(",baseDN=\"");
696        searchOperation.getBaseDN().toString(buffer);
697        buffer.append("\",scope=");
698        buffer.append(scope.toString());
699        buffer.append(",filter=\"");
700        filter.toString(buffer);
701        buffer.append("\")");
702      }
703    }
704