<commit_before>require 'xmlrpc/client'
require 'active_resource'
require 'app/models/open_object_ui'
require 'app/models/uml'
require 'set'

#TODO implement passing session credentials to RPC methods (concurrent access of different user credentials in Rails)

class OpenObjectResource < ActiveResource::Base
  include UML

  # ******************** class methods ********************
  class << self

    cattr_accessor :logger
    attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
                  :fields, :fields_defined, :many2one_relations, :one2many_relations, :many2many_relations, :relations_keys,
                  :openerp_database, :user_id, :scope_prefix, :ooor

    def class_name_from_model_key(model_key=self.openerp_model)
      model_key.split('.').collect {|name_part| name_part.capitalize}.join
    end

    #similar to Object#const_get but for OpenERP model key
    def const_get(model_key)
      klass_name = class_name_from_model_key(model_key)
      klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model(model_key, nil, nil, nil, nil, self.scope_prefix)
      klass.reload_fields_definition unless klass.fields_defined
      klass
    end

    def reload_fields_definition(force = false)
      if not (self.to_s.match('IrModel') || self.to_s.match('IrModelFields')) and (force or not @fields_defined)#TODO have a way to force reloading @field_ids too eventually
        fields = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get('IrModelFields').find(@field_ids)
        @fields = {}
        fields.each do |field|
          case field.attributes['ttype']
          when 'many2one'
            @many2one_relations[field.attributes['name']] = field
          when 'one2many'
            @one2many_relations[field.attributes['name']] = field
          when 'many2many'
            @many2many_relations[field.attributes['name']] = field
          else
            @fields[field.attributes['name']] = field
          end
        end
        @relations_keys = @many2one_relations.merge(@one2many_relations).merge(@many2many_relations).keys
        logger.info "#{fields.size} fields loaded in model #{self.class}"
      end
      @fields_defined = true
    end

    # ******************** remote communication ********************

    #OpenERP search method
    def search(domain, offset=0, limit=false, order=false, context={}, count=false)
      rpc_execute('search', domain, offset, limit, order, context, count)
    end

    def client(url)
      @clients ||= {}
      @clients[url] ||= XMLRPC::Client.new2(url)
    end

    #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
    def rpc_execute(method, *args)
      rpc_execute_with_object(@openerp_model, method, *args)
    end

    def rpc_execute_with_object(object, method, *args)
      rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
    end

    #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
    def rpc_execute_with_all(db, uid, pass, obj, method, *args)
      clean_request_args!(args)
      logger.debug "rpc_execute_with_all: rpc_method: 'execute', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, method: #{method}, *args: #{args.inspect}"
      try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute",  db, uid, pass, obj, method, *args)) }
    end

     #corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
    def rpc_exec_workflow(action, *args)
      rpc_exec_workflow_with_object(@openerp_model, action, *args)
    end

    def rpc_exec_workflow_with_object(object, action, *args)
      rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || <PASSWORD>.config[:password], object, action, *args)
    end

    def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
      clean_request_args!(args)
      logger.debug "rpc_execute_with_all: rpc_method: 'exec_workflow', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, action: #{action}, *args: #{args.inspect}"
      try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args)) }
    end

    def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
      context = @ooor.global_context.merge(context)
      cast_request_to_openerp!(form)
      unless wizard_id
        logger.debug "rpc_execute_with_all: rpc_method: 'create old_wizard_step' #{wizard_name}"
        wizard_id = try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("create",  @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || <PASSWORD>[:password], wizard_name)) }
      end
      logger.debug "rpc_execute_with_all: rpc_method: 'execute old_wizard_step' #{wizard_id}, #{{'model' => @openerp_model, 'form' => form, 'id' => ids[0], 'report_type' => report_type, 'ids' => ids}.inspect}, #{step}, #{context}"
      [wizard_id, try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute",  @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || <PASSWORD>[:password], wizard_id, {'model' => @openerp_model, 'form' => form, 'id' => ids[0], 'report_type' => report_type, 'ids' => ids}, step, context)) }]
    end

    #grab the eventual error log from OpenERP response as OpenERP doesn't enforce carefuly
    #the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
    def try_with_pretty_error_log
      yield
      rescue RuntimeError => e
        begin
          openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
        rescue SyntaxError
          raise e
        end
        raise e unless openerp_error_hash.is_a? Hash
        logger.error "*********** OpenERP Server ERROR:\n#{openerp_error_hash["faultString"]}***********"
        raise RuntimeError.new('OpenERP server error')
    end

    def clean_request_args!(args)
      if args[-1].is_a? Hash
        args[-1] = @ooor.global_context.merge(args[-1])
      elsif args.is_a?(Array)
        args += [@ooor.global_context]
      end
      cast_request_to_openerp!(args[-2]) if args[-2].is_a? Hash
    end

    def cast_request_to_openerp!(map)
      map.each do |k, v|
        if !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
          map[k] = v.to_f
        elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
          map[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}"
        elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
          map[k] = "#{v.year}-#{v.month}-#{v.day}"
        end
      end
    end

    def cast_answer_to_ruby!(answer)
      reload_fields_definition() unless self.fields_defined

      def cast_map_to_ruby!(map)
        map.each do |k, v|
          if self.fields[k] && v.is_a?(String) && !v.empty?
            case self.fields[k].ttype
              when 'datetime'
                map[k] = Time.parse(v)
              when 'date'
                map[k] = Date.parse(v)
            end<commit_msg>even more defensive casting against nil values in requests to OpenERP<commit_after>require 'xmlrpc/client'
require 'active_resource'
require 'app/models/open_object_ui'
require 'app/models/uml'
require 'set'

#TODO implement passing session credentials to RPC methods (concurrent access of different user credentials in Rails)

class OpenObjectResource < ActiveResource::Base
  include UML

  # ******************** class methods ********************
  class << self

    cattr_accessor :logger
    attr_accessor :openerp_id, :info, :access_ids, :name, :openerp_model, :field_ids, :state, #model class attributes assotiated to the OpenERP ir.model
                  :fields, :fields_defined, :many2one_relations, :one2many_relations, :many2many_relations, :relations_keys,
                  :openerp_database, :user_id, :scope_prefix, :ooor

    def class_name_from_model_key(model_key=self.openerp_model)
      model_key.split('.').collect {|name_part| name_part.capitalize}.join
    end

    #similar to Object#const_get but for OpenERP model key
    def const_get(model_key)
      klass_name = class_name_from_model_key(model_key)
      klass = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_defined?(klass_name) ? (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get(klass_name) : @ooor.define_openerp_model(model_key, nil, nil, nil, nil, self.scope_prefix)
      klass.reload_fields_definition unless klass.fields_defined
      klass
    end

    def reload_fields_definition(force = false)
      if not (self.to_s.match('IrModel') || self.to_s.match('IrModelFields')) and (force or not @fields_defined)#TODO have a way to force reloading @field_ids too eventually
        fields = (self.scope_prefix ? Object.const_get(self.scope_prefix) : Object).const_get('IrModelFields').find(@field_ids)
        @fields = {}
        fields.each do |field|
          case field.attributes['ttype']
          when 'many2one'
            @many2one_relations[field.attributes['name']] = field
          when 'one2many'
            @one2many_relations[field.attributes['name']] = field
          when 'many2many'
            @many2many_relations[field.attributes['name']] = field
          else
            @fields[field.attributes['name']] = field
          end
        end
        @relations_keys = @many2one_relations.merge(@one2many_relations).merge(@many2many_relations).keys
        logger.info "#{fields.size} fields loaded in model #{self.class}"
      end
      @fields_defined = true
    end

    # ******************** remote communication ********************

    #OpenERP search method
    def search(domain, offset=0, limit=false, order=false, context={}, count=false)
      rpc_execute('search', domain, offset, limit, order, context, count)
    end

    def client(url)
      @clients ||= {}
      @clients[url] ||= XMLRPC::Client.new2(url)
    end

    #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
    def rpc_execute(method, *args)
      rpc_execute_with_object(@openerp_model, method, *args)
    end

    def rpc_execute_with_object(object, method, *args)
      rpc_execute_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], object, method, *args)
    end

    #corresponding method for OpenERP osv.execute(self, db, uid, obj, method, *args, **kw) method
    def rpc_execute_with_all(db, uid, pass, obj, method, *args)
      clean_request_args!(args)
      logger.debug "rpc_execute_with_all: rpc_method: 'execute', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.<PASSWORD>}, obj: #{obj.inspect}, method: #{method}, *args: #{args.inspect}"
      try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("execute",  db, uid, pass, obj, method, *args)) }
    end

     #corresponding method for OpenERP osv.exec_workflow(self, db, uid, obj, method, *args)
    def rpc_exec_workflow(action, *args)
      rpc_exec_workflow_with_object(@openerp_model, action, *args)
    end

    def rpc_exec_workflow_with_object(object, action, *args)
      rpc_exec_workflow_with_all(@database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || <PASSWORD>[:password], object, action, *args)
    end

    def rpc_exec_workflow_with_all(db, uid, pass, obj, action, *args)
      clean_request_args!(args)
      logger.debug "rpc_execute_with_all: rpc_method: 'exec_workflow', db: #{db.inspect}, uid: #{uid.inspect}, pass: #{pass.inspect}, obj: #{obj.inspect}, action: #{action}, *args: #{args.inspect}"
      try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/object").call("exec_workflow", db, uid, pass, obj, action, *args)) }
    end

    def old_wizard_step(wizard_name, ids, step='init', wizard_id=nil, form={}, context={}, report_type='pdf')
      context = @ooor.global_context.merge(context)
      cast_request_to_openerp!(form)
      unless wizard_id
        logger.debug "rpc_execute_with_all: rpc_method: 'create old_wizard_step' #{wizard_name}"
        wizard_id = try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("create",  @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || <PASSWORD>[:password], wizard_name)) }
      end
      logger.debug "rpc_execute_with_all: rpc_method: 'execute old_wizard_step' #{wizard_id}, #{{'model' => @openerp_model, 'form' => form, 'id' => ids[0], 'report_type' => report_type, 'ids' => ids}.inspect}, #{step}, #{context}"
      [wizard_id, try_with_pretty_error_log { cast_answer_to_ruby!(client((@database && @site || @ooor.base_url) + "/wizard").call("execute",  @database || @ooor.config[:database], @user_id || @ooor.config[:user_id], @password || @ooor.config[:password], wizard_id, {'model' => @openerp_model, 'form' => form, 'id' => ids[0], 'report_type' => report_type, 'ids' => ids}, step, context)) }]
    end

    #grab the eventual error log from OpenERP response as OpenERP doesn't enforce carefuly
    #the XML/RPC spec, see https://bugs.launchpad.net/openerp/+bug/257581
    def try_with_pretty_error_log
      yield
      rescue RuntimeError => e
        begin
          openerp_error_hash = eval("#{ e }".gsub("wrong fault-structure: ", ""))
        rescue SyntaxError
          raise e
        end
        raise e unless openerp_error_hash.is_a? Hash
        logger.error "*********** OpenERP Server ERROR:\n#{openerp_error_hash["faultString"]}***********"
        raise RuntimeError.new('OpenERP server error')
    end

    def clean_request_args!(args)
      if args[-1].is_a? Hash
        args[-1] = @ooor.global_context.merge(args[-1])
      elsif args.is_a?(Array)
        args += [@ooor.global_context]
      end
      cast_request_to_openerp!(args[-2]) if args[-2].is_a? Hash
    end

    def cast_request_to_openerp!(map)
      map.each do |k, v|
        if v == nil
          map[k] = false
        elsif !v.is_a?(Integer) && !v.is_a?(Float) && v.is_a?(Numeric) && v.respond_to?(:to_f)
          map[k] = v.to_f
        elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:sec) && v.respond_to?(:year)#really ensure that's a datetime type
          map[k] = "#{v.year}-#{v.month}-#{v.day} #{v.hour}:#{v.min}:#{v.sec}"
        elsif !v.is_a?(Numeric) && !v.is_a?(Integer) && v.respond_to?(:day) && v.respond_to?(:year)#really ensure that's a date type
          map[k] = "#{v.year}-#{v.month}-#{v.day}"
        end
      end
    end

    def cast_answer_to_ruby!(answer)
      reload_fields_definition() unless self.fields_defined

      def cast_map_to_ruby!(map)
        map.each do |k, v|
          if self.fields[k] && v.is_a?(String) && !v.empty?
            case self.fields[k].ttype
              when 'datetime'
                map[k] = Time.parse(v)
              when 'date'
                map[k] = Date.parse(v)
            end
{'fraction_non_alphanumeric': 0.1538558151218287, 'fraction_numerical': 0.003391107761868877, 'mean_word_length': 3.7311348781937017, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 31, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, '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': '115510', 'n_tokens_mistral': 5417, 'n_tokens_neox': 5302, 'n_words': 1102}