1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.authz.support;
21  
22  
23  import org.apache.directory.server.core.CoreSession;
24  import org.apache.directory.server.core.DefaultCoreSession;
25  import org.apache.directory.server.core.DefaultDirectoryService;
26  import org.apache.directory.server.core.DirectoryService;
27  import org.apache.directory.server.core.OperationManager;
28  import org.apache.directory.server.core.ReferralHandlingMode;
29  import org.apache.directory.server.core.authn.LdapPrincipal;
30  import org.apache.directory.server.core.changelog.ChangeLog;
31  import org.apache.directory.server.core.cursor.ClosureMonitor;
32  import org.apache.directory.server.core.cursor.Cursor;
33  import org.apache.directory.server.core.cursor.CursorIterator;
34  import org.apache.directory.server.core.entry.ClonedServerEntry;
35  import org.apache.directory.server.core.entry.DefaultServerEntry;
36  import org.apache.directory.server.core.entry.ServerEntry;
37  import org.apache.directory.server.core.event.EventService;
38  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
39  import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
40  import org.apache.directory.server.core.interceptor.Interceptor;
41  import org.apache.directory.server.core.interceptor.InterceptorChain;
42  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
43  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
44  import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
45  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
46  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
47  import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
48  import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
49  import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
50  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
51  import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
52  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
53  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
54  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
55  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
56  import org.apache.directory.server.core.interceptor.context.OperationContext;
57  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
58  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
59  import org.apache.directory.server.core.interceptor.context.UnbindOperationContext;
60  import org.apache.directory.server.core.partition.Partition;
61  import org.apache.directory.server.core.partition.PartitionNexus;
62  import org.apache.directory.server.core.schema.SchemaOperationControl;
63  import org.apache.directory.server.core.schema.SchemaService;
64  import org.apache.directory.server.schema.registries.Registries;
65  import org.apache.directory.shared.ldap.NotImplementedException;
66  import org.apache.directory.shared.ldap.aci.ACITuple;
67  import org.apache.directory.shared.ldap.aci.MicroOperation;
68  import org.apache.directory.shared.ldap.aci.ProtectedItem;
69  import org.apache.directory.shared.ldap.aci.UserClass;
70  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
71  import org.apache.directory.shared.ldap.entry.Modification;
72  import org.apache.directory.shared.ldap.ldif.LdifEntry;
73  import org.apache.directory.shared.ldap.name.LdapDN;
74  
75  import javax.naming.NamingException;
76  import javax.naming.ldap.Control;
77  import javax.naming.ldap.LdapContext;
78  import java.io.File;
79  import java.util.ArrayList;
80  import java.util.Collection;
81  import java.util.Collections;
82  import java.util.HashSet;
83  import java.util.Hashtable;
84  import java.util.Iterator;
85  import java.util.List;
86  import java.util.NoSuchElementException;
87  import java.util.Set;
88  
89  import org.junit.Test;
90  import org.junit.BeforeClass;
91  import static org.junit.Assert.assertEquals;
92  
93  
94  /**
95   * Tests {@link MaxImmSubFilter}.
96   *
97   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
98   * @version $Rev: 687551 $, $Date: 2008-08-21 06:20:28 +0200 (Do, 21 Aug 2008) $
99   */
100 public class MaxImmSubFilterTest
101 {
102     private static final Collection<ACITuple> EMPTY_ACI_TUPLE_COLLECTION = Collections.unmodifiableCollection( new ArrayList<ACITuple>() );
103     private static final Collection<UserClass> EMPTY_USER_CLASS_COLLECTION = Collections.unmodifiableCollection( new ArrayList<UserClass>() );
104     private static final Collection<ProtectedItem> EMPTY_PROTECTED_ITEM_COLLECTION = Collections.unmodifiableCollection( new ArrayList<ProtectedItem>() );
105 
106     private static final Set<MicroOperation> EMPTY_MICRO_OPERATION_SET = Collections.unmodifiableSet( new HashSet<MicroOperation>() );
107 
108     private static final LdapDN ROOTDSE_NAME = new LdapDN();
109     private static LdapDN ENTRY_NAME;
110     private static Collection<ProtectedItem> PROTECTED_ITEMS = new ArrayList<ProtectedItem>();
111     private static ServerEntry ENTRY;
112     
113     /** A reference to the directory service */
114     private static DirectoryService service;
115 
116     
117     @BeforeClass public static void setup() throws NamingException
118     {
119         service = new DefaultDirectoryService();
120 
121         ENTRY_NAME = new LdapDN( "ou=test, ou=system" );
122         PROTECTED_ITEMS.add( new ProtectedItem.MaxImmSub( 2 ) );
123         ENTRY = new DefaultServerEntry( service.getRegistries(), ENTRY_NAME );
124     }
125 
126 
127     @Test public void testWrongScope() throws Exception
128     {
129         MaxImmSubFilter filter = new MaxImmSubFilter();
130         Collection<ACITuple> tuples = new ArrayList<ACITuple>();
131         tuples.add( new ACITuple( EMPTY_USER_CLASS_COLLECTION, AuthenticationLevel.NONE, 
132             EMPTY_PROTECTED_ITEM_COLLECTION, EMPTY_MICRO_OPERATION_SET, true, 0 ) );
133 
134         tuples = Collections.unmodifiableCollection( tuples );
135 
136         assertEquals( tuples, filter.filter( null, tuples, OperationScope.ATTRIBUTE_TYPE, null, null, null, null,
137             null, ENTRY_NAME, null, null, ENTRY, null, null ) );
138 
139         assertEquals( tuples, filter.filter( null, tuples, OperationScope.ATTRIBUTE_TYPE_AND_VALUE, null, null, null,
140             null, null, ENTRY_NAME, null, null, ENTRY, null, null ) );
141     }
142 
143 
144     @Test public void testRootDSE() throws Exception
145     {
146         MaxImmSubFilter filter = new MaxImmSubFilter();
147 
148         Collection<ACITuple> tuples = new ArrayList<ACITuple>();
149         tuples.add( new ACITuple( EMPTY_USER_CLASS_COLLECTION, AuthenticationLevel.NONE, 
150             EMPTY_PROTECTED_ITEM_COLLECTION, EMPTY_MICRO_OPERATION_SET, true, 0 ) );
151 
152         tuples = Collections.unmodifiableCollection( tuples );
153 
154         assertEquals( tuples, filter.filter( null, tuples, OperationScope.ENTRY, null, null, null, null, null,
155             ROOTDSE_NAME, null, null, ENTRY, null, null ) );
156     }
157 
158 
159     @Test public void testZeroTuple() throws Exception
160     {
161         MaxImmSubFilter filter = new MaxImmSubFilter();
162 
163         assertEquals( 0, filter.filter( null, EMPTY_ACI_TUPLE_COLLECTION, OperationScope.ENTRY, null, null, null, null, null,
164             ENTRY_NAME, null, null, ENTRY, null, null ).size() );
165     }
166 
167 
168     @Test public void testDenialTuple() throws Exception
169     {
170         MaxImmSubFilter filter = new MaxImmSubFilter();
171         Collection<ACITuple> tuples = new ArrayList<ACITuple>();
172         tuples.add( new ACITuple( EMPTY_USER_CLASS_COLLECTION, AuthenticationLevel.NONE, 
173             PROTECTED_ITEMS, EMPTY_MICRO_OPERATION_SET, false, 0 ) );
174 
175         tuples = Collections.unmodifiableCollection( tuples );
176 
177         assertEquals( tuples, filter.filter( null, tuples, OperationScope.ENTRY, null, null, null, null, null,
178             ENTRY_NAME, null, null, ENTRY, null, null ) );
179     }
180 
181 
182     @Test public void testGrantTuple() throws Exception
183     {
184         MaxImmSubFilter filter = new MaxImmSubFilter();
185         Collection<ACITuple> tuples = new ArrayList<ACITuple>();
186         tuples.add( new ACITuple( EMPTY_USER_CLASS_COLLECTION, AuthenticationLevel.NONE, 
187             PROTECTED_ITEMS, EMPTY_MICRO_OPERATION_SET, true, 0 ) );
188 
189         assertEquals( 1, filter.filter( null, tuples, OperationScope.ENTRY, new MockOperation( 1 ), null, null, null,
190             null, ENTRY_NAME, null, null, ENTRY, null, null ).size() );
191 
192         assertEquals( 0, filter.filter( null, tuples, OperationScope.ENTRY, new MockOperation( 3 ), null, null, null,
193             null, ENTRY_NAME, null, null, ENTRY, null, null ).size() );
194     }
195 
196     
197     class MockOperation implements OperationContext
198     {
199         final int count;
200         final CoreSession session; 
201 
202 
203         public MockOperation( int count ) throws Exception 
204         {
205             this.count = count;
206             this.session = new DefaultCoreSession( new LdapPrincipal( new LdapDN(), AuthenticationLevel.STRONG ), 
207                 new MockDirectoryService( count ) );
208         }
209 
210 
211         public EntryFilteringCursor search( SearchOperationContext opContext )
212             throws NamingException
213         {
214             return new BaseEntryFilteringCursor( new BogusCursor( count ), opContext );
215         }
216 
217 
218         public EntryFilteringCursor search( SearchOperationContext opContext, Collection<String> bypass ) throws NamingException
219         {
220             return new BaseEntryFilteringCursor( new BogusCursor( count ), opContext );
221         }
222 
223 
224         public void addRequestControl( Control requestControl )
225         {
226         }
227 
228 
229         public void addRequestControls( Control[] requestControls )
230         {
231         }
232 
233 
234         public void addResponseControl( Control responseControl )
235         {
236         }
237 
238 
239         public Collection<String> getByPassed()
240         {
241             return null;
242         }
243 
244 
245         public LdapDN getDn()
246         {
247             return null;
248         }
249 
250 
251         public String getName()
252         {
253             return null;
254         }
255 
256 
257         public Control getRequestControl( String numericOid )
258         {
259             return null;
260         }
261 
262 
263         public Control getResponseControl( String numericOid )
264         {
265             return null;
266         }
267 
268 
269         public int getResponseControlCount()
270         {
271             return 0;
272         }
273 
274 
275         public Control[] getResponseControls()
276         {
277             return null;
278         }
279 
280 
281         public CoreSession getSession()
282         {
283             return session;
284         }
285 
286 
287         public boolean hasBypass()
288         {
289             return false;
290         }
291 
292 
293         public boolean hasRequestControl( String numericOid )
294         {
295             return false;
296         }
297 
298 
299         public boolean hasRequestControls()
300         {
301             return false;
302         }
303 
304 
305         public boolean hasResponseControl( String numericOid )
306         {
307             return false;
308         }
309 
310 
311         public boolean hasResponseControls()
312         {
313             return false;
314         }
315 
316 
317         public boolean isBypassed( String interceptorName )
318         {
319             return false;
320         }
321 
322 
323         public boolean isCollateralOperation()
324         {
325             return false;
326         }
327 
328 
329         public ClonedServerEntry lookup( LdapDN dn, Collection<String> bypass ) throws Exception
330         {
331             return null;
332         }
333 
334 
335         public ClonedServerEntry lookup( LookupOperationContext lookupContext ) throws Exception
336         {
337             return null;
338         }
339 
340 
341         public LookupOperationContext newLookupContext( LdapDN dn )
342         {
343             return null;
344         }
345 
346 
347         public void setByPassed( Collection<String> byPassed )
348         {
349         }
350 
351 
352         public void setCollateralOperation( boolean collateralOperation )
353         {
354         }
355 
356 
357         public void setDn( LdapDN dn )
358         {
359         }
360 
361 
362         public LdapPrincipal getEffectivePrincipal()
363         {
364             return null;
365         }
366 
367 
368         public OperationContext getFirstOperation()
369         {
370             return null;
371         }
372 
373 
374         public OperationContext getLastOperation()
375         {
376             return null;
377         }
378 
379 
380         public OperationContext getNextOperation()
381         {
382             return null;
383         }
384 
385 
386         public OperationContext getPreviousOperation()
387         {
388             return null;
389         }
390 
391 
392         public boolean isFirstOperation()
393         {
394             return false;
395         }
396 
397 
398         public void add( ServerEntry entry, Collection<String> bypass ) throws Exception
399         {
400         }
401 
402 
403         public void delete( LdapDN dn, Collection<String> bypass ) throws Exception
404         {
405         }
406 
407 
408         public void modify( LdapDN dn, List<Modification> mods, Collection<String> bypass ) throws Exception
409         {
410         }
411 
412 
413         public boolean hasEntry( LdapDN dn, Collection<String> byPass ) throws Exception
414         {
415             return false;
416         }
417 
418 
419         public ReferralHandlingMode getReferralHandlingMode()
420         {
421             return null;
422         }
423 
424 
425         public void setReferralHandlingMode( ReferralHandlingMode referralHandlingMode )
426         {
427         }
428 
429 
430         public ClonedServerEntry getEntry()
431         {
432             return null;
433         }
434 
435 
436         public void setEntry( ClonedServerEntry entry )
437         {
438         }
439     }
440 
441     class MockDirectoryService implements DirectoryService
442     {
443         int count;
444         
445         
446         public MockDirectoryService( int count )
447         {
448             this.count = count;
449         }
450         
451         public Hashtable<String, Object> getEnvironment()
452         {
453             return null;
454         }
455 
456 
457         public void setEnvironment( Hashtable<String, Object> environment )
458         {
459         }
460 
461 
462         public long revert( long revision ) throws NamingException
463         {
464             return 0;
465         }
466 
467 
468         public long revert() throws NamingException
469         {
470             return 0;
471         }
472 
473 
474         public PartitionNexus getPartitionNexus()
475         {
476             return null;
477         }
478 
479 
480         public InterceptorChain getInterceptorChain()
481         {
482             return null;
483         }
484 
485 
486         public void addPartition( Partition partition ) throws NamingException
487         {
488         }
489 
490 
491         public void removePartition( Partition partition ) throws NamingException
492         {
493         }
494 
495 
496         public Registries getRegistries()
497         {
498             return null;
499         }
500 
501 
502         public void setRegistries( Registries registries )
503         {
504         }
505 
506 
507         public SchemaService getSchemaService()
508         {
509             return null;
510         }
511 
512 
513         public void setSchemaService( SchemaService schemaService )
514         {
515 
516         }
517 
518 
519         public SchemaOperationControl getSchemaManager()
520         {
521             return null;
522         }
523 
524 
525         public void setSchemaManager( SchemaOperationControl schemaManager )
526         {
527         }
528 
529 
530         public void startup() throws NamingException
531         {
532         }
533 
534 
535         public void shutdown() throws NamingException
536         {
537         }
538 
539 
540         public void sync() throws NamingException
541         {
542         }
543 
544 
545         public boolean isStarted()
546         {
547             return true;
548         }
549 
550 
551         public LdapContext getJndiContext() throws NamingException
552         {
553             return null;
554         }
555 
556 
557         public DirectoryService getDirectoryService()
558         {
559             return null;
560         }
561 
562 
563         public void setInstanceId( String instanceId )
564         {
565 
566         }
567 
568 
569         public String getInstanceId()
570         {
571             return null;
572         }
573 
574 
575         public Set<? extends Partition> getPartitions()
576         {
577             return null;
578         }
579 
580 
581         public void setPartitions( Set<? extends Partition> partitions )
582         {
583         }
584 
585 
586         public boolean isAccessControlEnabled()
587         {
588             return false;
589         }
590 
591 
592         public void setAccessControlEnabled( boolean accessControlEnabled )
593         {
594         }
595 
596 
597         public boolean isAllowAnonymousAccess()
598         {
599             return false;
600         }
601 
602 
603         public void setAllowAnonymousAccess( boolean enableAnonymousAccess )
604         {
605 
606         }
607 
608 
609         public List<Interceptor> getInterceptors()
610         {
611             return null;
612         }
613 
614 
615         public void setInterceptors( List<Interceptor> interceptors )
616         {
617 
618         }
619 
620 
621         public List<LdifEntry> getTestEntries()
622         {
623             return null;
624         }
625 
626 
627         public void setTestEntries( List<? extends LdifEntry> testEntries )
628         {
629         }
630 
631 
632         public File getWorkingDirectory()
633         {
634             return null;
635         }
636 
637 
638         public void setWorkingDirectory( File workingDirectory )
639         {
640         }
641 
642 
643         public void validate()
644         {
645         }
646 
647 
648         public void setShutdownHookEnabled( boolean shutdownHookEnabled )
649         {
650 
651         }
652 
653 
654         public boolean isShutdownHookEnabled()
655         {
656             return false;
657         }
658 
659 
660         public void setExitVmOnShutdown( boolean exitVmOnShutdown )
661         {
662 
663         }
664 
665 
666         public boolean isExitVmOnShutdown()
667         {
668             return false;
669         }
670 
671 
672         public void setMaxSizeLimit( int maxSizeLimit )
673         {
674 
675         }
676 
677 
678         public int getMaxSizeLimit()
679         {
680             return 0;
681         }
682 
683 
684         public void setMaxTimeLimit( int maxTimeLimit )
685         {
686 
687         }
688 
689 
690         public int getMaxTimeLimit()
691         {
692             return 0;
693         }
694 
695 
696         public void setSystemPartition( Partition systemPartition )
697         {
698 
699         }
700 
701 
702         public Partition getSystemPartition()
703         {
704             return null;
705         }
706 
707 
708         public boolean isDenormalizeOpAttrsEnabled()
709         {
710             return false;
711         }
712 
713 
714         public void setDenormalizeOpAttrsEnabled( boolean denormalizeOpAttrsEnabled )
715         {
716 
717         }
718         
719         public void setChangeLog( ChangeLog changeLog )
720         {
721             
722         }
723         
724         public ChangeLog getChangeLog()
725         {
726             return null;
727         }
728 
729 
730         public ServerEntry newEntry( LdapDN dn ) throws NamingException
731         {
732             return null;
733         }
734         
735         public ServerEntry newEntry( String ldif, String dn )
736         {
737             return null;
738         }
739 
740 
741         public OperationManager getOperationManager()
742         {
743             return new MockOperationManager( count );
744         }
745 
746 
747         public CoreSession getSession() throws Exception
748         {
749             return null;
750         }
751 
752 
753         public CoreSession getSession( LdapPrincipal principal ) throws Exception
754         {
755             return null;
756         }
757 
758 
759         public CoreSession getSession( LdapDN principalDn, byte[] credentials ) throws Exception
760         {
761             return null;
762         }
763 
764         
765         public CoreSession getSession( LdapDN principalDn, byte[] credentials, String saslMechanism, String saslAuthId )
766             throws Exception
767         {
768             return null;
769         }
770 
771         public CoreSession getAdminSession() throws Exception
772         {
773             return null;
774         }
775 
776         public EventService getEventService()
777         {
778             return null;
779         }
780 
781         public void setEventService( EventService eventService )
782         {
783         }
784     }
785 
786     
787     class MockOperationManager implements OperationManager
788     {
789         int count;
790         
791         public MockOperationManager( int count )
792         {
793             this.count = count;
794         }
795         
796         public void add( AddOperationContext opContext ) throws Exception
797         {
798         }
799 
800         
801         public void bind( BindOperationContext opContext ) throws Exception
802         {
803         }
804 
805         
806         public boolean compare( CompareOperationContext opContext ) throws Exception
807         {
808             return false;
809         }
810 
811 
812         public void delete( DeleteOperationContext opContext ) throws Exception
813         {
814         }
815 
816         public LdapDN getMatchedName( GetMatchedNameOperationContext opContext ) throws Exception
817         {
818             return null;
819         }
820 
821         public ClonedServerEntry getRootDSE( GetRootDSEOperationContext opContext ) throws Exception
822         {
823             return null;
824         }
825 
826         public LdapDN getSuffix( GetSuffixOperationContext opContext ) throws Exception
827         {
828             return null;
829         }
830 
831         public boolean hasEntry( EntryOperationContext opContext ) throws Exception
832         {
833             return false;
834         }
835 
836         public EntryFilteringCursor list( ListOperationContext opContext ) throws Exception
837         {
838             return null;
839         }
840 
841         public Iterator<String> listSuffixes( ListSuffixOperationContext opContext ) throws Exception
842         {
843             return null;
844         }
845 
846         public ClonedServerEntry lookup( LookupOperationContext opContext ) throws Exception
847         {
848             return null;
849         }
850 
851         public void modify( ModifyOperationContext opContext ) throws Exception
852         {
853         }
854 
855         public void move( MoveOperationContext opContext ) throws Exception
856         {
857         }
858 
859         public void moveAndRename( MoveAndRenameOperationContext opContext ) throws Exception
860         {
861         }
862 
863         public void rename( RenameOperationContext opContext ) throws Exception
864         {
865         }
866 
867         public EntryFilteringCursor search( SearchOperationContext opContext ) throws Exception
868         {
869             return new BaseEntryFilteringCursor( new BogusCursor( count ), opContext );
870         }
871 
872 
873         public void unbind( UnbindOperationContext opContext ) throws Exception
874         {
875         }
876     }
877     
878 
879     class BogusCursor implements Cursor<ServerEntry>
880     {
881         final int count;
882         int ii;
883 
884 
885         public BogusCursor(int count)
886         {
887             this.count = count;
888         }
889 
890 
891         public boolean available() 
892         {
893             return ii < count;
894         }
895 
896 
897         public void close() throws NamingException
898         {
899             ii = count;
900         }
901 
902 
903         public boolean hasMoreElements()
904         {
905             return ii < count;
906         }
907 
908 
909         public Object nextElement()
910         {
911             if ( ii >= count )
912             {
913                 throw new NoSuchElementException();
914             }
915 
916             ii++;
917             
918             return new Object();
919         }
920 
921 
922         public void after( ServerEntry element ) throws Exception
923         {
924         }
925 
926 
927         public void afterLast() throws Exception
928         {
929         }
930 
931 
932         public void before( ServerEntry element ) throws Exception
933         {
934             throw new NotImplementedException();
935         }
936 
937 
938         public void beforeFirst() throws Exception
939         {
940             ii = -1;
941         }
942 
943 
944         public boolean first() throws Exception
945         {
946             ii = 0;
947             return ii < count;
948         }
949 
950 
951         public ServerEntry get() throws Exception
952         {
953             return new DefaultServerEntry( service.getRegistries() );
954         }
955 
956 
957         public boolean isClosed() throws Exception
958         {
959             return false;
960         }
961 
962 
963         public boolean isElementReused()
964         {
965             return false;
966         }
967 
968 
969         public boolean last() throws Exception
970         {
971             ii = count;
972             return true;
973         }
974 
975 
976         public boolean next() 
977         {
978             if ( ii >= count )
979             {
980                 return false;
981             }
982 
983             ii++;
984             
985             return true;
986         }
987 
988 
989         public boolean previous() throws Exception
990         {
991             if ( ii < 0 )
992             {
993                 return false;
994             }
995             
996             ii--;
997             return true;
998         }
999 
1000 
1001         public Iterator<ServerEntry> iterator()
1002         {
1003             return new CursorIterator<ServerEntry>( this );
1004         }
1005 
1006 
1007         public void close( Exception reason ) throws Exception
1008         {
1009         }
1010 
1011 
1012         public void setClosureMonitor( ClosureMonitor monitor )
1013         {
1014         }
1015     }
1016 }