require 'dm-salesforce-adapter/connection/errors' module DataMapper module Adapters class SalesforceAdapter < DataObjectsAdapter class Connection include Errors class HeaderHandler < SOAP::Header::SimpleHandler def initialize(tag, value) super(XSD::QName.new('urn:enterprise.soap.sforce.com', tag)) @tag = tag @value = value end def on_simple_outbound @value end end def initialize(username, password, wsdl_path, api_dir, organization_id = nil) @wrapper = SoapWrapper.new("SalesforceAPI", "Soap", wsdl_path, api_dir) @username, @password, @organization_id = URI.unescape(username), password, organization_id login end attr_reader :user_id, :user_details def wsdl_path @wrapper.wsdl_path end def api_dir @wrapper.api_dir end def organization_id @user_details && @user_details.organizationId end def make_object(klass_name, values) obj = SalesforceAPI.const_get(klass_name).new values.each do |property, value| field = field_name_for(klass_name, property) if value.nil? or value == "" obj.fieldsToNull.push(field) else obj.send("#{field}=", value) end end obj end def field_name_for(klass_name, column) klass = SalesforceAPI.const_get(klass_name) # extlib vs. activesupport fields = [column, DataMapper::Inflector.camelize(column), "#{column}__c".downcase] options = /^(#{fields.join("|")})$/i matches = klass.instance_methods(false).grep(options) if matches.any? matches.first else raise FieldNotFound, "You specified #{column} as a field, but neither #{fields.join(" or ")} exist. " \ "Either manually specify the field name with :field, or check to make sure you have " \ "provided a correct field name." end end def query(string) with_reconnection do driver.query(:queryString => string).result end rescue SOAP::FaultError => e raise QueryError.new(e.message, []) end def create(objects) call_api(:create, CreateError, "creating", objects) end def update(objects) call_api(:update, UpdateError, "updating", objects) end def delete(keys) call_api(:delete, DeleteError, "deleting", keys) end private def driver @wrapper.driver end def login driver if @organization_id driver.headerhandler << HeaderHandler.new("LoginScopeHeader", :organizationId => @organization_id) end begin result = driver.login(:username => @username, :password => @password).result rescue SOAP::FaultError => error if error.to_s =~ /INVALID_LOGIN/ raise LoginFailed, error.inspect else raise error end end driver.endpoint_url = result.serverUrl driver.headerhandler << HeaderHandler.new("SessionHeader", "sessionId" => result.sessionId) driver.headerhandler << HeaderHandler.new("CallOptions", "client" => "client") @user_id = result.userId @user_details = result.userInfo driver end def call_api(method, exception_class, message, args) with_reconnection do result = driver.send(method, args) if result.all? {|r| r.success} result else # TODO: be smarter about exceptions here raise exception_class.new("Got some errors while #{message} Salesforce objects", result) end end end def with_reconnection(&block) yield rescue SOAP::FaultError => error retry_count ||= 0 if error.faultcode.to_s =~ "INVALID_SESSION_ID" DataMapper.logger.debug "Got a invalid session id; reconnecting" if DataMapper.logger @driver = nil login retry_count += 1 retry unless retry_count > 5 else raise error end raise SessionTimeout, "The Salesforce session could not be established" end end end end end