# An object that collects stored objects from the central cache and returns
# them to the current host, yo.
class Puppet::Parser::Collector
    attr_accessor :type, :scope, :vquery, :rquery, :form, :resources

    # Collect exported objects.
    def collect_exported
        # First get everything from the export table.  Just reuse our
        # collect_virtual method but tell it to use 'exported? for the test.
        resources = collect_virtual(true).reject { |r| ! r.virtual? }

        count = 0

        unless @scope.host
            raise Puppet::DevError, "Cannot collect resources for a nil host"
        end

        # We're going to collect objects from rails, but we don't want any
        # objects from this host.
        unless ActiveRecord::Base.connected?
            Puppet::Rails.init
        end
        host = Puppet::Rails::Host.find_by_name(@scope.host)

        args = {:include => {:param_values => :param_name}}
        args[:conditions] = "(exported = 't' AND restype = '%s')" % [@type]
        if @equery
            args[:conditions] += " AND (%s)" % [@equery]
        end
        if host
            args[:conditions] = "host_id != %s AND %s" % [host.id, args[:conditions]]
        else
            #Puppet.info "Host %s is uninitialized" % @scope.host
        end

        # Now look them up in the rails db.  When we support attribute comparison
        # and such, we'll need to vary the conditions, but this works with no
        # attributes, anyway.
        time = Puppet::Util.thinmark do
            Puppet::Rails::Resource.find(:all, @type, true,
                args
            ).each do |obj|
                if resource = export_resource(obj)
                    count += 1
                    resources << resource
                end
            end
        end

        scope.debug("Collected %s %s resource%s in %.2f seconds" %
            [count, @type, count == 1 ? "" : "s", time])

        return resources
    end

    def collect_resources
        unless @resources.is_a?(Array)
            @resources = [@resources]
        end
        method = "collect_#{form.to_s}_resources"
        send(method)
    end

    def collect_exported_resources
        raise Puppet::ParseError,
            "realize() is not yet implemented for exported resources"
    end

    # Collect resources directly; this is the result of using 'realize',
    # which specifies resources, rather than using a normal collection.
    def collect_virtual_resources
        result = @resources.dup.collect do |ref|
            if res = @scope.findresource(ref.to_s)
                @resources.delete(ref)
                res
            end
        end.reject { |r| r.nil? }.each do |res|
            res.virtual = false
        end

        # If there are no more resources to find, delete this from the list
        # of collections.
        if @resources.empty?
            @scope.collections.delete(self)
        end

        return result
    end

    # Collect just virtual objects, from our local configuration.
    def collect_virtual(exported = false)
        if exported
            method = :exported?
        else
            method = :virtual?
        end
        scope.resources.find_all do |resource|
            resource.type == @type and resource.send(method) and match?(resource)
        end
    end

    # Call the collection method, mark all of the returned objects as non-virtual,
    # and then delete this object from the list of collections to evaluate.
    def evaluate
        if self.resources
            if objects = collect_resources and ! objects.empty?
                return objects
            else
                return false
            end
        else
            method = "collect_#{@form.to_s}"
            objects = send(method).each { |obj| obj.virtual = false }
            if objects.empty?
                return false
            else
                return objects
            end
        end

#        if objects and ! objects.empty?
#            objects.each { |r| r.virtual = false }
#            return objects
#        else
#            return false
#        end
    end

    def initialize(scope, type, equery, vquery, form)
        @scope = scope
        @type = type
        @equery = equery
        @vquery = vquery
        @form = form
        @tests = []
    end

    # Does the resource match our tests?  We don't yet support tests,
    # so it's always true at the moment.
    def match?(resource)
        if self.vquery
            return self.vquery.call(resource)
        else
            return true
        end
    end

    def export_resource(obj)
        if existing = @scope.findresource(obj.restype, obj.title)
            # See if we exported it; if so, just move on
            if @scope.host == obj.host.name
                return nil
            else
                # Next see if we've already collected this resource
                if existing.rails_id == obj.id
                    # This is the one we've already collected
                    return nil
                else
                    raise Puppet::ParseError,
                        "Exported resource %s cannot override local resource" %
                        [obj.ref]
                end
            end
        end

        begin
            resource = obj.to_resource(self.scope)
            
            # XXX Because the scopes don't expect objects to return values,
            # we have to manually add our objects to the scope.  This is
            # über-lame.
            scope.setresource(resource)
        rescue => detail
            if Puppet[:trace]
                puts detail.backtrace
            end
            raise
        end
        resource.exported = false

        return resource
    end
end

# $Id: collector.rb 2746 2007-08-05 17:57:31Z luke $


syntax highlighted by Code2HTML, v. 0.9.1