A Treequel::Control module that implements the "LDAP Control Extension for Server Side Sorting of Search Results" (RFC 2891).
As with all Controls, you must first register the control with the Treequel::Directory object you're intending to search:
dir = Treequel.directory( 'ldap://ldap.acme.com/dc=acme,dc=com' ) dir.register_controls( Treequel::SortedResultsControl )
Once that's done, any Treequel::Branchset you create will have the #order method that will allow you to specify one or more attributes by which the server should sort results before returning them:
# Fetch people sorted by their last name, then first name, then # by employeeNumber people = dir.ou( :People ) sorted_people = people.filter( :objectClass => :person ). order( :sn, :givenName, :employeeNumber )
The ordering criteria, if any
Add the requisite instance variables to including Branchsets.
# File lib/treequel/controls/sortedresults.rb, line 89 def initialize self.log.debug "initializing %p" % [ self ] @sort_order_criteria = [] end
Override the Enumerable method to update the cookie value each time a page is fetched.
# File lib/treequel/controls/sortedresults.rb, line 153 def each( &block ) super do |branch| if sorted_control = branch.controls.find {|control| control.oid == RESPONSE_OID } sortResult, attributeType = sorted_control.decode if sortResult.nonzero? self.log.error "got non-zero response code for sort: %d (%s)" % [ sortResult, RESPONSE_RESULT_DESCRIPTIONS[sortResult] ] raise Treequel::ControlError, RESPONSE_RESULT_DESCRIPTIONS[sortResult] else self.log.debug "got 'success' sort response code." end end block.call( branch ) end end
Clone the Branchset with a server-side sorted results control added and return it.
# File lib/treequel/controls/sortedresults.rb, line 104 def order( *attributes ) self.log.warn "This control will likely not work in ruby-ldap versions " + " <= 0.9.9. See http://code.google.com/p/ruby-activeldap/issues/" + "detail?id=38 for details." if LDAP::PATCH_VERSION < 10 if attributes.flatten.empty? self.log.debug "cloning %p with no order" % [ self ] return self.unordered else criteria = attributes.collect do |attrspec| case attrspec when Symbol Criterion.new( attrspec.to_s ) when Sequel::SQL::Expression Criterion.new( attrspec.expression.to_s, nil, attrspec.descending ) else raise ArgumentError, "unsupported order specification type %s" % [ attrspec.class.name ] end end self.log.debug "cloning %p with order criteria: %p" % [ self, criteria ] copy = self.clone copy.sort_order_criteria += criteria return copy end end
Clone the Branchset without a server-side sorted results control and return it.
# File lib/treequel/controls/sortedresults.rb, line 137 def unordered copy = self.clone copy.unordered! return copy end
Remove any server-side sorted results control associated with the receiving Branchset, returning any removed criteria as an Array.
# File lib/treequel/controls/sortedresults.rb, line 146 def unordered! return self.sort_order_criteria.slice!( 0..-1 ) end
Treequel::Control API -- Get a configured LDAP::Control object for this Branchset.
# File lib/treequel/controls/sortedresults.rb, line 198 def get_server_controls controls = super criteria = self.sort_order_criteria if criteria.empty? self.log.debug "No sort order criteria; skipping the server-side sort control" else self.log.debug "Found %d sort order criteria; generating a server-side sort control" % [ criteria.length ] asn1_string = self.make_sorted_control_value( criteria ) controls << LDAP::Control.new( OID, asn1_string, true ) end return controls end
Make the ASN.1 string for the control value out of the given
mode
, cookie
, reload_hint
.
# File lib/treequel/controls/sortedresults.rb, line 177 def make_sorted_control_value( sort_criteria ) ### (http://tools.ietf.org/html/rfc2891#section-1.1): # SortKeyList ::= SEQUENCE OF SEQUENCE { # attributeType AttributeDescription, # orderingRule [0] MatchingRuleId OPTIONAL, # reverseOrder [1] BOOLEAN DEFAULT FALSE } encoded_vals = sort_criteria.collect do |criterion| seq = [] seq << OpenSSL::ASN1::OctetString.new( criterion.type ) seq << OpenSSL::ASN1::ObjectId.new( criterion.ordering_rule ) if criterion.ordering_rule seq << OpenSSL::ASN1::Boolean.new( true ) if criterion.reverse_order OpenSSL::ASN1::Sequence.new( seq ) end return OpenSSL::ASN1::Sequence.new( encoded_vals ).to_der end