1# Copyright (c) 2000-2004 Graham Barr <gbarr@pobox.com>. All rights reserved.
2# This program is free software; you can redistribute it and/or
3# modify it under the same terms as Perl itself.
4
5package Net::LDAP::Control::VLV;
6
7use vars qw(@ISA $VERSION);
8use Net::LDAP::Control;
9
10@ISA = qw(Net::LDAP::Control);
11$VERSION = "0.03";
12
13use Net::LDAP::ASN qw(VirtualListViewRequest);
14use strict;
15
16sub init {
17  my($self) = @_;
18
19  # VLVREQUEST should always have a critical of true
20  $self->{'critical'} = 1 unless exists $self->{'critical'};
21
22  if (exists $self->{value}) {
23    $self->value($self->{value});
24  }
25  else {
26    my $asn = $self->{asn} = {};
27
28    $asn->{beforeCount} = $self->{before} || 0;
29    $asn->{afterCount}  = $self->{after} || 0;
30    if (exists $self->{assert}) {
31      $asn->{byValue} = $self->{assert};
32    }
33    else {
34      $asn->{byoffset} = {
35 	offset => $self->{offset} || 0,
36	contentCount => $self->{content} || 0
37      };
38    }
39  }
40
41  $self;
42}
43
44sub before {
45  my $self = shift;
46  if (@_) {
47    delete $self->{value};
48    return $self->{asn}{beforeCount} = shift;
49  }
50  $self->{asn}{beforeCount};
51}
52
53sub after  {
54  my $self = shift;
55  if (@_) {
56    delete $self->{value};
57    return $self->{asn}{afterCount} = shift;
58  }
59  $self->{asn}{afterCount};
60}
61
62sub content {
63  my $self = shift;
64  if (@_) {
65    delete $self->{value};
66    if (exists $self->{asn}{byValue}) {
67      delete $self->{asn}{byValue};
68      $self->{asn}{byoffset} = { offset => 0 };
69    }
70    return $self->{asn}{byoffset}{contentCount} = shift;
71  }
72  exists $self->{asn}{byoffset}
73    ? $self->{asn}{byoffset}{contentCount}
74    : undef;
75}
76
77sub assert {
78  my $self = shift;
79  if (@_) {
80    delete $self->{value};
81    delete $self->{asn}{byoffset};
82    return $self->{asn}{byValue} = shift;
83  }
84  exists $self->{asn}{byValue}
85    ? $self->{asn}{byValue}
86    : undef;
87}
88
89sub context {
90  my $self = shift;
91  if (@_) {
92    delete $self->{value};
93    return $self->{asn}{contextID} = shift;
94  }
95  $self->{asn}{contextID};
96}
97
98# Update self with values from a response
99
100sub response {
101  my $self = shift;
102  my $resp = shift;
103
104  my $asn = $self->{asn};
105
106  $asn->{contextID} = $resp->context;
107  $asn->{byoffset} = {
108    offset => $resp->target,
109    contentCount => $resp->content
110  };
111  delete $asn->{byValue};
112
113  1;
114}
115
116sub offset {
117  my $self = shift;
118  if (@_) {
119    delete $self->{value};
120    if (exists $self->{asn}{byValue}) {
121      delete $self->{asn}{byValue};
122      $self->{asn}{byoffset} = { contentCount => 0 };
123    }
124    return $self->{asn}{byoffset}{offset} = shift;
125  }
126  exists $self->{asn}{byoffset}
127    ? $self->{asn}{byoffset}{offset}
128    : undef;
129}
130
131sub value {
132  my $self = shift;
133
134  if (@_) {
135    unless ($self->{asn} = $VirtualListViewRequest->decode($_[0])) {
136      delete $self->{value};
137      return undef;
138    }
139    $self->{value} = shift;
140  }
141
142  exists $self->{value}
143    ? $self->{value}
144    : $self->{value} = $VirtualListViewRequest->encode($self->{asn});
145}
146
147sub scroll {
148  my $self = shift;
149  my $n = shift;
150  my $asn = $self->{asn};
151  my $byoffset = $asn->{byoffset}
152    or return undef;
153  my $offset = $byoffset->{offset} + $n;
154  my $content;
155
156  if ($offset < 1) {
157    $asn->{afterCount} += $asn->{beforeCount};
158    $asn->{beforeCount} = 0;
159    $offset = $byoffset->{offset} = 1;
160  }
161  elsif ($byoffset->{contentCount} and $asn->{afterCount}+$offset >$byoffset->{contentCount}) {
162    if ($offset > $byoffset->{contentCount}) {
163      $offset = $byoffset->{offset} = $byoffset->{contentCount};
164      $asn->{beforeCount} += $asn->{afterCount};
165      $asn->{afterCount} = 0;
166    }
167    else {
168      my $tmp = $byoffset->{contentCount} - $offset;
169      $asn->{beforeCount} += $tmp;
170      $asn->{afterCount}  -= $tmp;
171      $byoffset->{offset} = $offset;
172    }
173  }
174  else {
175    $byoffset->{offset} = $offset;
176  }
177
178  $offset;
179}
180
181sub scroll_page {
182  my $self = shift;
183  my $n = shift;
184  my $asn = $self->{asn};
185  my $page_size = $asn->{beforeCount} + $asn->{afterCount} + 1;
186
187  $self->scroll( $page_size * $n);
188}
189
190sub start {
191  my $self = shift;
192  my $asn = $self->{asn};
193  $asn->{afterCount} += $asn->{beforeCount};
194  $asn->{beforeCount} = 0;
195  $self->offset(1);
196}
197
198sub end {
199  my $self = shift;
200  my $asn = $self->{asn};
201  my $content = $self->content || 0;
202
203  $asn->{beforeCount} += $asn->{afterCount};
204  $asn->{afterCount} = 0;
205  $self->offset($content);
206}
207
2081;
209
210__END__
211
212=head1 NAME
213
214Net::LDAP::Control::VLV - LDAPv3 Virtual List View control object
215
216=head1 SYNOPSIS
217
218 use Net::LDAP;
219 use Net::LDAP::Control::VLV;
220 use Net::LDAP::Constant qw( LDAP_CONTROL_VLVRESPONSE );
221
222 $ldap = Net::LDAP->new( "ldap.mydomain.eg" );
223
224 # Get the first 20 entries
225 $vlv  = Net::LDAP::Control::VLV->new(
226	   before  => 0,	# No entries from before target entry
227	   after   => 19,	# 19 entries after target entry
228	   content => 0,	# List size unknown
229	   offset  => 1,	# Target entry is the first
230	 );
231 $sort = Net::LDAP::Control::Sort->new( order => 'cn' );
232
233 @args = ( base     => "o=Ace Industry, c=us",
234	   scope    => "subtree",
235	   filter   => "(objectClass=inetOrgPerson)",
236	   callback => \&process_entry, # Call this sub for each entry
237	   control  => [ $vlv, $sort ],
238 );
239
240 $mesg = $ldap->search( @args );
241
242 # Get VLV response control
243 ($resp)  = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
244 $vlv->response( $resp );
245
246 # Set the control to get the last 20 entries
247 $vlv->end;
248
249 $mesg = $ldap->search( @args );
250
251 # Get VLV response control
252 ($resp)  = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
253 $vlv->response( $resp );
254
255 # Now get the previous page
256 $vlv->scroll_page( -1 );
257
258 $mesg = $ldap->search( @args );
259
260 # Get VLV response control
261 ($resp)  = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
262 $vlv->response( $resp );
263
264 # Now page with first entry starting with "B" in the middle
265 $vlv->before(9);	# Change page to show 9 before
266 $vlv->after(10);	# Change page to show 10 after
267 $vlv->assert("B");	# assert "B"
268
269 $mesg = $ldap->search( @args );
270
271=head1 DESCRIPTION
272
273C<Net::LDAP::Control::VLV> provides an interface for the creation and
274manipulation of objects that represent the Virtual List View as described
275by draft-ietf-ldapext-ldapv3-vlv-03.txt.
276
277When using a Virtual List View control in a search, it must be accompanied by a sort
278control. See L<Net::LDAP::Control::Sort>
279
280=cut
281
282##
283## Need some blurb here to describe the VLV control. Maybe extract some simple
284## describtion from the draft RFC
285##
286
287=head1 CONSTRUCTOR ARGUMENTS
288
289In addition to the constructor arguments described in
290L<Net::LDAP::Control> the following are provided.
291
292=over 4
293
294=item after
295
296Set the number of entries the server should return from the list after
297the target entry.
298
299=item assert
300
301Set the assertion value user to locate the target entry. This value should
302be a legal value to compare with the first attribute in the sort control
303that is passed with the VLV control. The target entry is the first entry
304in the list which is greater than or equal the assert value.
305
306=item before
307
308Set the number of entries the server should return from the list before
309the target entry.
310
311=item content
312
313Set the number of entries in the list. On the first search this value
314should be set to zero. On subsequent searches it should be set to the
315length of the list, as returned by the server in the VLVResponse control.
316
317=item context
318
319Set the context identifier.  On the first search this value should be
320set to zero. On subsequent searches it should be set to the context
321value returned by the server in the VLVResponse control.
322
323=item offset
324
325Set the offset of the target entry.
326
327=back
328
329=head2 METHODS
330
331As with L<Net::LDAP::Control> each constructor argument
332described above is also avaliable as a method on the object which will
333return the current value for the attribute if called without an argument,
334and set a new value for the attribute if called with an argument.
335
336The C<offset> and C<assert> attributes are mutually exclusive. Setting
337one or the other will cause previous values set by the other to
338be forgotten. The C<content> attribute is also associated with the
339C<offset> attribute, so setting C<assert> will cause any C<content>
340value to be forgotten.
341
342=over 4
343
344=item end
345
346Set the target entry to the end of the list. This method will change the C<before>
347and C<after> attributes so that the target entry is the last in the page.
348
349=item response VLV_RESPONSE
350
351Set the attributes in the control as per VLV_RESPONSE. VLV_RESPONSE should be a control
352of type L<Net::LDAP::Control::VLVResponse> returned
353from the server. C<response> will populate the C<context>, C<offset> and C<content>
354attibutes of the control with the values from VLV_RESPONSE. Because this sets the
355C<offset> attribute, any previous setting of the C<assert> attribute will be forgotten.
356
357=item scroll NUM
358
359Move the target entry by NUM entries. A positive NUM will move the target entry towards
360the end of the list and a negative NUM will move the target entry towards the
361start of the list. Returns the index of the new target entry, or C<undef> if the current target
362is identified by an assertion.
363
364C<scroll> may change the C<before> and C<after> attributes if the scroll value would
365cause the page to go off either end of the list. But the page size will be maintained.
366
367=item scroll_page NUM
368
369Scroll by NUM pages. This method simple calculates the current page size and calls
370C<scroll> with C<NUM * $page_size>
371
372=item start
373
374Set the target entry to the start of the list. This method will change the C<before> and C<after>
375attributes to the the target entry is the first entry in the page.
376
377=back
378
379=head1 SEE ALSO
380
381L<Net::LDAP>,
382L<Net::LDAP::Control>,
383L<Net::LDAP::Control::Sort>,
384L<Net::LDAP::Control::VLVResponse>
385
386=head1 AUTHOR
387
388Graham Barr E<lt>gbarr@pobox.comE<gt>
389
390Please report any bugs, or post any suggestions, to the perl-ldap mailing list
391E<lt>perl-ldap@perl.orgE<gt>
392
393=head1 COPYRIGHT
394
395Copyright (c) 2000-2004 Graham Barr. All rights reserved. This program is
396free software; you can redistribute it and/or modify it under the same
397terms as Perl itself.
398
399=cut
400