<commit_before># SPQR:  Schema Processor for QMF/Ruby agents
#
# Application skeleton class
#
# Copyright (c) 2009 Red Hat, Inc.
#
# Author:  <NAME> (<EMAIL>)
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0

require 'spqr/spqr'
require 'qmf'
require 'logger'

module SPQR
  class App < Qmf::AgentHandler
    class ClassMeta < Struct.new(:object_class, :schema_class) ; end

    def initialize(options=nil)
      defaults = {:logfile=>STDERR, :loglevel=>Logger::WARN, :notifier=>nil}
      
      # convenient shorthands for log levels
      loglevels = {:debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :error => Logger::ERROR, :fatal => Logger::FATAL}
        
      options = defaults unless options

      # set unsupplied options to defaults
      defaults.each do |k,v|
        options[k] = v unless options[k]
      end

      # fix up shorthands
      options[:loglevel] = loglevels[options[:loglevel]] if loglevels[options[:loglevel]]

      @log = Logger.new(options[:logfile])
      @log.level = options[:loglevel]

      @log.info("initializing SPQR app....")

      @classes_by_name = {}
      @classes_by_id = {}
      @pipe = options[:notifier]
      @app_name = (options[:appname] or "SPQR application")

    end

    def register(*ks)
      manageable_ks = ks.select {|kl| manageable? kl}
      unmanageable_ks = ks.select {|kl| not manageable? kl}
      manageable_ks.each do |klass|
        @log.info("SPQR will manage registered class #{klass} (#{klass.spqr_meta.classname})...")
        
        schemaclass = schematize(klass)

        klass.log = @log

        @classes_by_id[klass.class_id] = klass
        @classes_by_name[klass.spqr_meta.classname.to_s] = ClassMeta.new(klass, schemaclass)
      end
      
      unmanageable_ks.each do |klass|
        @log.warn("SPQR can't manage #{klass}, which was registered")
      end
    end


    def method_call(context, name, obj_id, args, user_id)
      begin
        class_id = obj_id.object_num_high
        obj_id = obj_id.object_num_low

        @log.debug "calling method: context=#{context} method=#{name} object_id=#{obj_id}, args=#{args}, user=#{user_id}"

        managed_object = find_object(context, class_id, obj_id)
        @log.debug("managed object is #{managed_object}")
        managed_method = managed_object.class.spqr_meta.mmethods[name.to_sym]

        raise RuntimeError.new("#{managed_object.class} does not have #{name} exposed as a manageable method; has #{managed_object.class.spqr_meta.mmethods.inspect}") unless managed_method

        # Extract actual parameters from the Qmf::Arguments structure into a proper ruby list

        # XXX: consider adding appropriate impl method to Manageable
        # to avoid this little dance
        actuals_in = managed_method.formals_in.inject([]) {|acc,nm| acc << args[nm]}
        actuals_in = actuals_in[0] if actuals_in.size == 1

        @log.debug("managed_object.respond_to? #{managed_method.name.to_sym} ==> #{managed_object.respond_to? managed_method.name.to_sym}")
        @log.debug("managed_object.class.spqr_meta.mmethods.include? #{name.to_sym} ==> #{managed_object.class.spqr_meta.mmethods.include? name.to_sym}")
        @log.debug("formals:  #{managed_method.formals_in.inspect}")
        @log.debug("actuals:  #{actuals_in.inspect}")

        actuals_out = actuals_in.size > 0 ? managed_object.send(name.to_sym, actuals_in) : managed_object.send(name.to_sym)

        raise RuntimeError.new("#{managed_object.class} did not return the appropriate number of return values; got '#{actuals_out.inspect}', but expected #{managed_method.types_out.inspect}") unless result_valid(actuals_out, managed_method)
        
        if managed_method.formals_out.size == 0
          actuals_out = [] # ignore return value in this case
        elsif managed_method.formals_out.size == 1
          actuals_out = [actuals_out] # wrap this up in a list
        end
        
        @log.debug("formals_out == #{managed_method.formals_out.inspect}")
        @log.debug("actuals_out == #{actuals_out.inspect}")

        # Copy any out parameters from return value to the
        # Qmf::Arguments structure; see XXX above
        managed_method.formals_out.zip(actuals_out).each do |k,v|
          @log.debug("fixing up out params:  #{k.inspect} --> #{v.inspect}")
          encoded_val = encode_object(v)
          args[k] = encoded_val
        end

        @agent.method_response(context, 0, "OK", args)
      rescue Exception => ex
        @log.error "Error calling #{name}: #{ex}"
        @log.error "    " + ex.backtrace.join("\n    ")
        @agent.method_response(context, 1, "ERROR: #{ex}", args)
      end
    end

    def get_query(context, query, user_id)
      @log.debug "query: user=#{user_id} context=#{context} class=#{query.class_name} object_num=#{query.object_id.object_num_low if query.object_id} details=#{query} haveSelect=#{query.impl and query.impl.haveSelect} getSelect=#{query.impl and query.impl.getSelect} (#{query.impl and query.impl.getSelect and query.impl.getSelect.methods.inspect})"

      @log.debug "classes_by_name is #{@classes_by_name.inspect}"

      cmeta = @classes_by_name[query.class_name]
      objs = []
      
      # XXX:  are these cases mutually exclusive?
      
      # handle queries for a certain class
      if cmeta
        objs = objs + cmeta.object_class.find_all.collect {|obj| qmfify(obj)}
      end

      # handle queries for a specific object
      o = find_object(context, query.object_id.object_num_high, query.object_id.object_num_low) rescue nil
      if o
        objs << qmfify(o)
      end

      objs.each do |obj| 
        @log.debug("query_response of: #{obj.inspect}")
        @agent.query_response(context, obj) rescue @log.error($!.inspect)
      end
      
      @log.debug("completing query....")
      @agent.query_complete(context)
    end

    def main
      # XXX:  fix and parameterize as necessary
      @log.debug("starting SPQR::App.main...")
      
      settings = Qmf::ConnectionSettings.new
      settings.host = 'localhost'
      
      @connection = Qmf::Connection.new(settings)<commit_msg>app skeleton now allows specifying username, password, host, and port<commit_after># SPQR:  Schema Processor for QMF/Ruby agents
#
# Application skeleton class
#
# Copyright (c) 2009 Red Hat, Inc.
#
# Author:  <NAME> (<EMAIL>)
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0

require 'spqr/spqr'
require 'qmf'
require 'logger'

module SPQR
  class App < Qmf::AgentHandler
    class ClassMeta < Struct.new(:object_class, :schema_class) ; end

    def initialize(options=nil)
      defaults = {:logfile=>STDERR, :loglevel=>Logger::WARN, :notifier=>nil, :server=>"localhost", :port=>5672}
      
      # convenient shorthands for log levels
      loglevels = {:debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :error => Logger::ERROR, :fatal => Logger::FATAL}
        
      options = defaults unless options

      # set unsupplied options to defaults
      defaults.each do |k,v|
        options[k] = v unless options[k]
      end

      # fix up shorthands
      options[:loglevel] = loglevels[options[:loglevel]] if loglevels[options[:loglevel]]

      @log = Logger.new(options[:logfile])
      @log.level = options[:loglevel]

      @log.info("initializing SPQR app....")

      @classes_by_name = {}
      @classes_by_id = {}
      @pipe = options[:notifier]
      @app_name = (options[:appname] or "SPQR application")
      @qmf_host = options[:server]
      @qmf_port = options[:port]
      @qmf_sendUserId = (options[:user] or options[:password])
      
      @qmf_user = options[:user]
      @qmf_password = options[:password]
    end

    def register(*ks)
      manageable_ks = ks.select {|kl| manageable? kl}
      unmanageable_ks = ks.select {|kl| not manageable? kl}
      manageable_ks.each do |klass|
        @log.info("SPQR will manage registered class #{klass} (#{klass.spqr_meta.classname})...")
        
        schemaclass = schematize(klass)

        klass.log = @log

        @classes_by_id[klass.class_id] = klass
        @classes_by_name[klass.spqr_meta.classname.to_s] = ClassMeta.new(klass, schemaclass)
      end
      
      unmanageable_ks.each do |klass|
        @log.warn("SPQR can't manage #{klass}, which was registered")
      end
    end


    def method_call(context, name, obj_id, args, user_id)
      begin
        class_id = obj_id.object_num_high
        obj_id = obj_id.object_num_low

        @log.debug "calling method: context=#{context} method=#{name} object_id=#{obj_id}, args=#{args}, user=#{user_id}"

        managed_object = find_object(context, class_id, obj_id)
        @log.debug("managed object is #{managed_object}")
        managed_method = managed_object.class.spqr_meta.mmethods[name.to_sym]

        raise RuntimeError.new("#{managed_object.class} does not have #{name} exposed as a manageable method; has #{managed_object.class.spqr_meta.mmethods.inspect}") unless managed_method

        # Extract actual parameters from the Qmf::Arguments structure into a proper ruby list

        # XXX: consider adding appropriate impl method to Manageable
        # to avoid this little dance
        actuals_in = managed_method.formals_in.inject([]) {|acc,nm| acc << args[nm]}
        actuals_in = actuals_in[0] if actuals_in.size == 1

        @log.debug("managed_object.respond_to? #{managed_method.name.to_sym} ==> #{managed_object.respond_to? managed_method.name.to_sym}")
        @log.debug("managed_object.class.spqr_meta.mmethods.include? #{name.to_sym} ==> #{managed_object.class.spqr_meta.mmethods.include? name.to_sym}")
        @log.debug("formals:  #{managed_method.formals_in.inspect}")
        @log.debug("actuals:  #{actuals_in.inspect}")

        actuals_out = actuals_in.size > 0 ? managed_object.send(name.to_sym, actuals_in) : managed_object.send(name.to_sym)

        raise RuntimeError.new("#{managed_object.class} did not return the appropriate number of return values; got '#{actuals_out.inspect}', but expected #{managed_method.types_out.inspect}") unless result_valid(actuals_out, managed_method)
        
        if managed_method.formals_out.size == 0
          actuals_out = [] # ignore return value in this case
        elsif managed_method.formals_out.size == 1
          actuals_out = [actuals_out] # wrap this up in a list
        end
        
        @log.debug("formals_out == #{managed_method.formals_out.inspect}")
        @log.debug("actuals_out == #{actuals_out.inspect}")

        # Copy any out parameters from return value to the
        # Qmf::Arguments structure; see XXX above
        managed_method.formals_out.zip(actuals_out).each do |k,v|
          @log.debug("fixing up out params:  #{k.inspect} --> #{v.inspect}")
          encoded_val = encode_object(v)
          args[k] = encoded_val
        end

        @agent.method_response(context, 0, "OK", args)
      rescue Exception => ex
        @log.error "Error calling #{name}: #{ex}"
        @log.error "    " + ex.backtrace.join("\n    ")
        @agent.method_response(context, 1, "ERROR: #{ex}", args)
      end
    end

    def get_query(context, query, user_id)
      @log.debug "query: user=#{user_id} context=#{context} class=#{query.class_name} object_num=#{query.object_id.object_num_low if query.object_id} details=#{query} haveSelect=#{query.impl and query.impl.haveSelect} getSelect=#{query.impl and query.impl.getSelect} (#{query.impl and query.impl.getSelect and query.impl.getSelect.methods.inspect})"

      @log.debug "classes_by_name is #{@classes_by_name.inspect}"

      cmeta = @classes_by_name[query.class_name]
      objs = []
      
      # XXX:  are these cases mutually exclusive?
      
      # handle queries for a certain class
      if cmeta
        objs = objs + cmeta.object_class.find_all.collect {|obj| qmfify(obj)}
      end

      # handle queries for a specific object
      o = find_object(context, query.object_id.object_num_high, query.object_id.object_num_low) rescue nil
      if o
        objs << qmfify(o)
      end

      objs.each do |obj| 
        @log.debug("query_response of: #{obj.inspect}")
        @agent.query_response(context, obj) rescue @log.error($!.inspect)
      end
      
      @log.debug("completing query....")
      @agent.query_complete(context)
    end

    def main
      # XXX:  fix and parameterize as necessary
      @log.debug("starting SPQR::App.main...")
      
      settings = Qmf::ConnectionSettings.new
      settings.host = @qmf_host
      settings.port = @qmf_port
      settings.sendUserId = @qmf_sendUserId
      
      settings.username = @qmf_user if @qmf_sendUserId
      settings.password = <PASSWORD> if @qmf_sendUserId
      
      @connection = Qmf::Connection.new(settings)
{'fraction_non_alphanumeric': 0.12307809407824631, 'fraction_numerical': 0.0025879129243416045, 'mean_word_length': 3.072845629262244, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1149429', 'n_tokens_mistral': 4248, 'n_tokens_neox': 4146, 'n_words': 962}