class Treequel::Branch
The object in Treequel
that wraps an entry. It knows how to construct other branches for the entries below itself, and how to search for those entries.
Constants
- DEFAULT_LDIF_WIDTH
The default width of LDIF output
- LDIF_FOLD_SEPARATOR
The characters to use to fold an LDIF line (newline + a space)
Attributes
The directory the branch's entry lives in
The DN of the branch.
The DN of the branch.
Public Class Methods
Create a new Treequel::Branch
with the given directory
, dn
, and an optional entry
. If the optional entry
object is given, it will be used to fetch values from the directory; if it isn't provided, it will be fetched from the directory
the first time it is needed.
# File lib/treequel/branch.rb, line 79 def initialize( directory, dn, entry=nil ) raise ArgumentError, "nil DN" unless dn raise ArgumentError, "invalid DN" unless dn.match( Patterns::DISTINGUISHED_NAME ) || dn.empty? raise ArgumentError, "can't cast a %s to an LDAP::Entry" % [entry.class.name] unless entry.nil? || entry.is_a?( Hash ) @directory = directory @dn = dn @entry = entry ? stringify_keys( entry ) : nil @values = {} @include_operational_attrs = self.class.include_operational_attrs? self.log.debug "New branch (%s): entry = %p, directory = %p" % [ @dn, @entry, @directory ] end
Create a new Treequel::Branch
from the given entry
hash from the specified directory
.
# File lib/treequel/branch.rb, line 60 def self::new_from_entry( entry, directory ) entry = Treequel::HashUtilities.stringify_keys( entry ) dnvals = entry.delete( 'dn' ) or raise ArgumentError, "no 'dn' attribute for entry" Treequel.logger.debug "Creating Branch from entry: %p in directory: %s" % [ dnvals.first, directory ] return self.new( directory, dnvals.first, entry ) end
Public Instance Methods
Addition operator: return a Treequel::BranchCollection
that contains both the receiver and other_branch
.
# File lib/treequel/branch.rb, line 493 def +( other_branch ) return Treequel::BranchCollection.new( self.branchset, other_branch.branchset ) end
Comparable interface: Returns -1 if other_branch is less than, 0 if other_branch
is equal to, and +1 if other_branch
is greater than the receiving Branch
.
# File lib/treequel/branch.rb, line 463 def <=>( other_branch ) # Try the easy cases first return nil unless other_branch.respond_to?( :dn ) && other_branch.respond_to?( :split_dn ) return 0 if other_branch.dn == self.dn # Try comparing reversed attribute pairs rval = nil pairseq = self.split_dn.reverse.zip( other_branch.split_dn.reverse ) pairseq.each do |a,b| comparison = (a <=> b) return comparison if !comparison.nil? && comparison.nonzero? end # The branches are related, so directly comparing DN strings will work return self.dn <=> other_branch.dn end
Fetch the value/s associated with the given attrname
from the underlying entry.
# File lib/treequel/branch.rb, line 300 def []( attrname ) attrsym = attrname.to_sym if @values.key?( attrsym ) # self.log.debug " value for %p is cached (%p)." % [ attrname, @values[attrsym] ] else self.log.debug " value for %p is NOT cached." % [ attrsym ] value = self.get_converted_object( attrsym ) self.log.debug " converted value is: %p" % [ value ] value.freeze if self.class.freeze_converted_values? && value.respond_to?( :freeze ) @values[ attrsym ] = value if value end return @values[ attrsym ] end
Set attribute attrname
to a new value
.
# File lib/treequel/branch.rb, line 331 def []=( attrname, value ) value = [ value ] unless value.is_a?( Array ) value.collect! {|val| self.get_converted_attribute(attrname, val) } self.log.debug "Modifying %s to %p" % [ attrname, value ] self.directory.modify( self, attrname.to_s => value ) @values.delete( attrname.to_sym ) self.entry[ attrname.to_s ] = value end
Return a Treequel::Branchset
that will use the receiver as its base.
# File lib/treequel/branch.rb, line 215 def branchset return Treequel::Branchset.new( self ) end
Return the Branch's immediate children as Treeque::Branch objects.
# File lib/treequel/branch.rb, line 209 def children return self.search( :one, '(objectClass=*)' ) end
Copy the entry for this Branch
to a new entry with the given newdn
and merge in the specified attributes
.
# File lib/treequel/branch.rb, line 418 def copy( newdn, attributes={} ) # Fully-qualify RDNs newdn = newdn + ',' + self.parent_dn unless newdn.index(',') self.log.debug "Creating a copy of %p at %p" % [ self.dn, newdn ] newbranch = self.class.new( self.directory, newdn ) attributes = self.entry.merge( stringify_keys(attributes) ) self.log.debug " merged attributes: %p" % [ attributes ] self.directory.create( newbranch, attributes ) return newbranch end
Create the entry for this Branch
with the specified attributes
. The attributes
should, at a minimum, contain the pair `:objectClass => [:someStructuralObjectClass]`.
groups = dir.ou( :groups ) newgroup = groups.cn( :staff ) newgroup.create( :objectClass => ['posixGroup'], :gidNumber => 2100 ) # => #<Treequel::Branch:0x1086a0ac8 cn=staff,ou=groups,dc=example,dc=com>
# File lib/treequel/branch.rb, line 409 def create( attributes={} ) self.directory.create( self, attributes ) self.clear_caches return self end
Delete the specified attributes
, which are the attributes to delete either as attribute names (in which case all values of the attribute are deleted), or Hashes of attributes and the Array of value/s which should be deleted.
# Delete all 'description' attributes branch.delete( :description ) # Delete the 'inetOrgPerson' and 'posixAccount' objectClasses from the entry branch.delete( :objectClass => [:inetOrgPerson, :posixAccount] ) # Delete any blank 'description' or 'cn' attributes: branch.delete( :description => '', :cn => '' )
# File lib/treequel/branch.rb, line 366 def delete( *attributes ) # If no attributes are given, delete the whole entry if attributes.empty? self.log.info "No attributes specified; deleting entire entry for %s" % [ self.dn ] self.directory.delete( self ) # Otherwise, gather up the LDAP::Mod objects that will delete the given attributes else self.log.debug "Deleting attributes: %p" % [ attributes ] mods = attributes.flatten.collect do |attribute| # Delete particular values of the attribute if attribute.is_a?( Hash ) attribute.collect do |key,vals| vals = [ vals ] unless vals.is_a?( Array ) vals.collect! {|val| self.get_converted_attribute(key, val) } LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, key.to_s, vals ) end # Delete all values of the attribute else LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, attribute.to_s, [] ) end end self.directory.modify( self, mods.flatten ) end self.clear_caches return true end
Change the DN the Branch
uses to look up its entry to newdn
.
# File lib/treequel/branch.rb, line 121 def dn=( newdn ) self.clear_caches @dn = newdn end
Return the LDAP::Entry associated with the receiver, fetching it from the directory if necessary. Returns nil
if the entry doesn't exist in the directory.
# File lib/treequel/branch.rb, line 145 def entry @entry ||= self.lookup_entry end
Comparison-by-value method – returns true
if the receiver has the same DN as other
.
# File lib/treequel/branch.rb, line 449 def eql?( other ) return false unless other.class.eql?( self.class ) return self.hash == other.hash end
Returns true
if there is an entry currently in the directory with the branch's DN.
# File lib/treequel/branch.rb, line 152 def exists? return self.entry ? true : false end
Fetch a new Treequel::Branch
object for the child of the receiver with the specified rdn
.
# File lib/treequel/branch.rb, line 484 def get_child( rdn ) self.log.debug "Getting child %p from base = %p" % [ rdn, self.dn ] newdn = [ rdn, self.dn ].reject {|part| part.empty? }.join( ',' ) return self.class.new( self.directory, newdn ) end
Generates a Fixnum hash for the receiver.
# File lib/treequel/branch.rb, line 456 def hash return [ self.class, self.dn ].hash end
Enable (if new_setting
is true) or disable fetching of operational attributes (RC4512, section 3.4).
# File lib/treequel/branch.rb, line 129 def include_operational_attrs=( new_setting ) self.clear_caches @include_operational_attrs = new_setting ? true : false end
Returns a human-readable representation of the object suitable for debugging.
# File lib/treequel/branch.rb, line 222 def inspect return "#<%s:0x%0x %s @ %s entry=%p>" % [ self.class.name, self.object_id * 2, self.dn, self.directory, @entry, ] end
Returns true
if the Branch's entry has been fetched from the directory.
# File lib/treequel/branch.rb, line 158 def loaded? return @entry ? true : false end
Return Treequel::Schema::AttributeType
instances for each of the receiver's objectClass's MAY attributeTypes. If any additional_object_classes
are given, include the MAY attributeTypes for them as well. This can be used to predict what optional attributes could be added to the entry if the additional_object_classes
were added to it.
# File lib/treequel/branch.rb, line 588 def may_attribute_types( *additional_object_classes ) return self.object_classes( *additional_object_classes ). collect {|oc| oc.may }.flatten.uniq end
Return a Hash of the optional attributes allowed by the Branch's objectClasses. If any additional_object_classes
are given, include the attributes that would be available for the entry if it had them.
# File lib/treequel/branch.rb, line 608 def may_attributes_hash( *additional_object_classes ) entry = self.entry attrhash = {} self.may_attribute_types( *additional_object_classes ).each do |attrtype| # self.log.debug " adding attrtype %p to the MAY attributes hash" % [ attrtype.named ] if attrtype.single? attrhash[ attrtype.name ] = nil else attrhash[ attrtype.name ] = [] end end # :FIXME: Does the resulting hash need the additional objectClasses? objectClass is # MUST via 'top', so it should already exist in that hash when merged with # this one... # attrhash[ :objectClass ] |= additional_object_classes return attrhash end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's objectClass's MAY attributeTypes. If any additional_object_classes
are given, include the OIDs of the MAY attributes for them as well. This can be used to predict what optional attributes could be added to the entry if the additional_object_classes
were added to it.
# File lib/treequel/branch.rb, line 599 def may_oids( *additional_object_classes ) return self.object_classes( *additional_object_classes ). collect {|oc| oc.may_oids }.flatten.uniq end
Make the changes to the entry specified by the given attributes
.
branch.merge( :description => ['The syadmin group'], :cn => ['sysadmin'] )
# File lib/treequel/branch.rb, line 344 def merge( attributes ) self.directory.modify( self, attributes ) self.clear_caches return true end
Move the entry associated with this branch to a new entry indicated by rdn
. If any attributes
are given, also replace the corresponding attributes on the new entry with them.
# File lib/treequel/branch.rb, line 438 def move( rdn ) self.log.debug "Asking the directory to move me to an entry called %p" % [ rdn ] self.directory.move( self, rdn ) self.clear_caches return self end
Return Treequel::Schema::AttributeType
instances for each of the receiver's objectClass's MUST attributeTypes. If any additional_object_classes
are given, include the MUST attributeTypes for them as well. This can be used to predict what attributes would need to be present for the entry to be saved if it added the additional_object_classes
to its own.
# File lib/treequel/branch.rb, line 542 def must_attribute_types( *additional_object_classes ) oclasses = self.object_classes( *additional_object_classes ) types = oclasses.map( &:must ).flatten.uniq return types end
Return a Hash of the attributes required by the Branch's objectClasses. If any additional_object_classes
are given, include the attributes that would be necessary for the entry to be saved with them.
# File lib/treequel/branch.rb, line 564 def must_attributes_hash( *additional_object_classes ) attrhash = {} self.must_attribute_types( *additional_object_classes ).each do |attrtype| # self.log.debug " adding attrtype %p to the MUST attributes hash" % [ attrtype.name ] if attrtype.name == :objectClass attrhash[ :objectClass ] = ['top'] | additional_object_classes elsif attrtype.single? attrhash[ attrtype.name ] = '' else attrhash[ attrtype.name ] = [''] end end return attrhash end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's objectClass's MUST attributeTypes. If any additional_object_classes
are given, include the OIDs of the MUST attributes for them as well. This can be used to predict what attributes would need to be present for the entry to be saved if it added the additional_object_classes
to its own.
# File lib/treequel/branch.rb, line 555 def must_oids( *additional_object_classes ) return self.object_classes( *additional_object_classes ). collect {|oc| oc.must_oids }.flatten.uniq.reject {|val| val == '' } end
Return Treequel::Schema::ObjectClass
instances for each of the receiver's objectClass attributes. If any additional_classes
are given, merge them with the current list of the current objectClasses for the lookup.
# File lib/treequel/branch.rb, line 501 def object_classes( *additional_classes ) # self.log.debug "Fetching object classes for %s" % [ self.dn ] schema = self.directory.schema oc_oids = self[:objectClass] || [] oc_oids |= additional_classes.collect {|str| str.to_sym } oc_oids << 'top' if oc_oids.empty? oclasses = [] oc_oids.each do |oid| oc = schema.object_classes[ oid.to_sym ] or raise Treequel::Error, "schema doesn't have a %p objectClass" % [ oid ] oclasses << oc end # self.log.debug " found %d objectClasses: %p" % [ oclasses.length, oclasses.map(&:name) ] return oclasses.uniq end
Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's operational attributes.
# File lib/treequel/branch.rb, line 529 def operational_attribute_oids return self.operational_attribute_types.inject([]) do |oids, attrtype| oids.push( *attrtype.names ) oids << attrtype.oid end end
Return the receiver's operational attributes as attributeType schema objects.
# File lib/treequel/branch.rb, line 522 def operational_attribute_types return self.directory.schema.operational_attribute_types end
Return the Branch's immediate parent node.
# File lib/treequel/branch.rb, line 194 def parent pardn = self.parent_dn or return nil return self.class.new( self.directory, pardn ) end
Return the DN of this entry's parent, or nil if it doesn't have one.
# File lib/treequel/branch.rb, line 186 def parent_dn return nil if self.dn == self.directory.base_dn return '' if self.dn.index( ',' ).nil? return self.split_dn( 2 ).last end
Return the RDN of the branch.
# File lib/treequel/branch.rb, line 164 def rdn return self.split_dn( 2 ).first end
Return the attribute/s which make up this Branch's RDN as a Hash.
# File lib/treequel/branch.rb, line 137 def rdn_attributes return make_rdn_hash( self.rdn ) end
Perform a search with the specified scope
, filter
, and parameters
using the receiver as the base. See Trequel::Directory#search for details. Returns an Array of Treequel::Branch
objects.
# File lib/treequel/branch.rb, line 203 def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block ) return self.directory.search( self, scope, filter, parameters, &block ) end
Return the receiver's DN as an Array of attribute=value pairs. If the optional limit
is non-zero, only the limit-1
first pairs are split from the DN, and the remainder will be returned as the last element.
# File lib/treequel/branch.rb, line 172 def split_dn( limit=0 ) return self.dn.split( /\s*,\s*/, limit ) end
Return the Branch
as a Hash.
# File lib/treequel/branch.rb, line 284 def to_hash entry = self.entry || self.valid_attributes_hash self.log.debug " making a Hash from an entry: %p" % [ entry ] return entry.keys.inject({}) do |hash, attribute| if attribute == 'dn' hash[ attribute ] = self.dn else hash[ attribute ] = self[ attribute ] end hash end end
Return the Branch
as an LDAP::LDIF::Entry.
# File lib/treequel/branch.rb, line 267 def to_ldif( width=DEFAULT_LDIF_WIDTH ) ldif = "dn: %s\n" % [ self.dn ] entry = self.entry || self.valid_attributes_hash self.log.debug " making LDIF from an entry: %p" % [ entry ] entry.keys.reject {|k| k == 'dn' }.each do |attribute| Array( entry[attribute] ).each do |val| ldif << ldif_for_attr( attribute, val, width ) end end return LDAP::LDIF::Entry.new( ldif ) end
Return the entry's DN as an RFC1781-style UFN (User-Friendly Name).
# File lib/treequel/branch.rb, line 234 def to_ufn if LDAP.respond_to?( :dn2ufn ) return LDAP.dn2ufn( self.dn ) # An implementation for LDAP libraries with no # dn2ufn else ufn = '' tuples = self.split_dn # Separate the trailing domainComponents dcs = [] dcs << tuples.pop while tuples.last =~ /^dc\s*=/i # Append the non-dc tuples with their attributes stripped first ufn << tuples.collect do |rdn| rdn. gsub(/\b#{ATTRIBUTE_TYPE}\s*=/, ''). gsub(/\s*\+\s*/, ' + ') end.join( ', ' ) # Now append the DCs joined with dots unless dcs.empty? ufn << ', ' ufn << dcs.reverse.map {|rdn| rdn.sub(/dc\s*=\s*/i, '') }.join( '.' ) end return ufn end end
Return true
if the specified attrname
is a valid attributeType given the receiver's current objectClasses. Does not include operational attributes.
# File lib/treequel/branch.rb, line 659 def valid_attribute?( attroid ) return !self.valid_attribute_type( attroid ).nil? end
Return a uniqified Array of OIDs (numeric OIDs as Strings, named OIDs as Symbols) for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.
# File lib/treequel/branch.rb, line 643 def valid_attribute_oids return self.must_oids | self.may_oids end
If the attribute associated with the given attroid
is in the list of valid attributeTypes for the receiver given its objectClasses, return the AttributeType object that corresponds with it. If it isn't valid, return nil. Includes operational attributes.
# File lib/treequel/branch.rb, line 652 def valid_attribute_type( attroid ) return self.valid_attribute_types.find {|attr_type| attr_type.valid_name?(attroid) } end
Return Treequel::Schema::AttributeType
instances for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.
# File lib/treequel/branch.rb, line 633 def valid_attribute_types return self.must_attribute_types | self.may_attribute_types | self.operational_attribute_types end
Return a Hash of all the attributes allowed by the Branch's objectClasses. If any additional_object_classes
are given, include the attributes that would be available for the entry if it had them.
# File lib/treequel/branch.rb, line 667 def valid_attributes_hash( *additional_object_classes ) self.log.debug "Gathering a hash of all valid attributes:" must = self.must_attributes_hash( *additional_object_classes ) self.log.debug " MUST attributes: %p" % [ must ] may = self.may_attributes_hash( *additional_object_classes ) self.log.debug " MAY attributes: %p" % [ may ] return may.merge( must ) end
Fetch one or more values for the specified attributes
from the entry.
branch.values_at( :cn, :objectClass ) => [["sysadmin"], ["top", "posixGroup", "apple-group"]]
# File lib/treequel/branch.rb, line 323 def values_at( *attributes ) return attributes.collect do |attribute| self[ attribute ] end end
Protected Instance Methods
Clear any cached values when the structural state of the object changes.
# File lib/treequel/branch.rb, line 778 def clear_caches self.log.debug "Clearing entry and values caches." @entry = nil @values.clear end
Convert the specified object
according to the Branch's directory's conversion rules, and return it.
# File lib/treequel/branch.rb, line 765 def get_converted_attribute( attrsym, object ) if attribute = self.directory.schema.attribute_types[ attrsym ] self.log.debug "converting %p object (a %p) to a %s attribute" % [ attrsym, object.class, attribute.syntax.desc ] return self.directory.convert_to_attribute( attribute.syntax_oid, object ) else self.log.info "no attributeType for %p" % [ attrsym ] return object.to_s end end
Get the value associated with attrsym
, convert it to a Ruby object if the Branch's directory has a conversion rule, and return it.
# File lib/treequel/branch.rb, line 741 def get_converted_object( attrsym ) value = self.entry ? self.entry[ attrsym.to_s ] : nil if attribute = self.directory.schema.attribute_types[ attrsym ] syntax = attribute.syntax syntax_oid = syntax.oid if syntax if attribute.single? value = self.directory.convert_to_object( syntax_oid, value.first ) if value else value = Array( value ).collect do |raw| self.directory.convert_to_object( syntax_oid, raw ) end end else self.log.info "no attributeType for %p" % [ attrsym ] end return value end
Fetch the entry from the Branch's directory.
# File lib/treequel/branch.rb, line 721 def lookup_entry self.log.debug "Looking up entry for %s" % [ self.dn ] entry = nil if self.include_operational_attrs? self.log.debug " including operational attributes." entry = self.directory.get_extended_entry( self ) else self.log.debug " not including operational attributes." entry = self.directory.get_entry( self ) end entry.delete( 'dn' ) if entry self.log.debug " entry is: %p" % [ entry ] return entry end
Proxy method: call traverse_branch
if attribute
is a valid attribute and value
isn't nil
.
# File lib/treequel/branch.rb, line 684 def method_missing( attribute, value=nil, additional_attributes={} ) return super( attribute ) if value.nil? return self.traverse_branch( attribute, value, additional_attributes ) end
If attribute
matches a valid attribute type in the directory's schema, return a new Branch
for the RDN of attribute
and value
and additional_attributes
(if it's a multi-value RDN).
# (Called via #method_missing) branch = Treequel::Branch.new( directory, 'ou=people,dc=acme,dc=com' ) branch.uid( :chester ).dn # => 'uid=chester,ou=people,dc=acme,dc=com' branch.uid( :chester, :employeeType => 'admin' ).dn # => 'uid=chester+employeeType=admin,ou=people,dc=acme,dc=com'
Raises a NoMethodError if the attribute
or any additional_attributes
are not valid attributeTypes.
# File lib/treequel/branch.rb, line 703 def traverse_branch( attribute, value, additional_attributes={} ) valid_types = self.directory.schema.attribute_types # Raise if either the primary attribute or any secondary attributes are invalid if !valid_types.key?( attribute ) raise NoMethodError, "undefined method `%s' for %p" % [ attribute, self ] elsif invalid = additional_attributes.keys.find {|ex_attr| !valid_types.key?(ex_attr) } raise NoMethodError, "invalid secondary attribute `%s' for %p" % [ invalid, self ] end # Make a normalized RDN from the arguments and return the Branch for it rdn = rdn_from_pair_and_hash( attribute, value, additional_attributes ) return self.get_child( rdn ) end
Private Instance Methods
Make LDIF for the given attribute
and its values
, wrapping at the given width
.
# File lib/treequel/branch.rb, line 826 def ldif_for_attr( attribute, value, width ) unsplit_line = "#{attribute}:" if value.empty? || value =~ /\A#{LDIF_SAFE_STRING}\Z/ unsplit_line << ' ' << value.to_s else unsplit_line << ': ' << [ value ].pack( 'm' ).chomp end unsplit_line.gsub!( /\n/, '' ) ldif = '' ldif << unsplit_line.slice!( 0, width ) << LDIF_FOLD_SEPARATOR until unsplit_line.empty? ldif.rstrip! ldif << "\n" return ldif end
Given an RDN
, return a Hash of the key/value pairs which make it up.
# File lib/treequel/branch.rb, line 815 def make_rdn_hash( rdn ) return rdn.split( /\s*\+\s*/ ).inject({}) do |attributes, pair| attrname, value = pair.split(/\s*=\s*/) attributes[ attrname ] = [ value ] attributes end end
Split the given rdn
into an Array of the iniital RDN attribute and value, and a Hash containing any additional pairs.
# File lib/treequel/branch.rb, line 801 def pair_and_hash_from_rdn( rdn ) initial, *trailing = rdn.split( '+' ) initial_pair = initial.split( /\s*=\s*/ ) trailing_pairs = trailing.inject({}) do |hash,pair| k,v = pair.split( /\s*=\s*/ ) hash[ k ] = v hash end return initial_pair + [ trailing_pairs ] end
Make an RDN string (RFC 4514) from the primary attribute
and value
pair plus any additional_attributes
(for multivalue RDNs).
# File lib/treequel/branch.rb, line 791 def rdn_from_pair_and_hash( attribute, value, additional_attributes={} ) additional_attributes.merge!( attribute => value ) return additional_attributes.sort_by {|k,v| k.to_s }. collect {|pair| pair.join('=') }. join('+') end