module RForce
# Implements the connection to the SalesForce server.
class Binding
include RForce
DEFAULT_BATCH_SIZE = 10
attr_accessor :batch_size, :url, :assignment_rule_id, :use_default_rule, :update_mru, :client_id, :trigger_user_email,
:trigger_other_email, :trigger_auto_response_email
# Fill in the guts of this typical SOAP envelope
# with the session ID and the body of the SOAP request.
Envelope = <<-HERE
xmlns:spartner="urn:sobject.partner.soap.sforce.com">
%s
%d
%s
%s
HERE
AssignmentRuleHeaderUsingRuleId = '%s'
AssignmentRuleHeaderUsingDefaultRule = 'true'
MruHeader = 'true'
ClientIdHeader = '%s'
# Connect to the server securely.
def initialize(url, sid = nil)
init_server(url)
@session_id = sid
@batch_size = DEFAULT_BATCH_SIZE
end
def show_debug
ENV['SHOWSOAP'] == 'true'
end
def init_server(url)
@url = URI.parse(url)
@server = Net::HTTP.new(@url.host, @url.port)
@server.use_ssl = @url.scheme == 'https'
@server.verify_mode = OpenSSL::SSL::VERIFY_NONE
# run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
@server.set_debug_output $stderr if show_debug
end
# Log in to the server and remember the session ID
# returned to us by SalesForce.
def login(user, password)
@user = user
@password = password
response = call_remote(:login, [:username, user, :password, password])
raise "Incorrect user name / password [#{response[:Fault][:faultstring]}]" unless response.loginResponse
result = response[:loginResponse][:result]
@session_id = result[:sessionId]
init_server(result[:serverUrl])
response
end
# Call a method on the remote server. Arguments can be
# a hash or (if order is important) an array of alternating
# keys and values.
def call_remote(method, args)
# Create XML text from the arguments.
expanded = ''
@builder = Builder::XmlMarkup.new(:target => expanded)
expand(@builder, {method => args}, 'urn:partner.soap.sforce.com')
extra_headers = ""
extra_headers << (AssignmentRuleHeaderUsingRuleId % assignment_rule_id) if assignment_rule_id
extra_headers << AssignmentRuleHeaderUsingDefaultRule if use_default_rule
extra_headers << MruHeader if update_mru
extra_headers << (ClientIdHeader % client_id) if client_id
if trigger_user_email or trigger_other_email or trigger_auto_response_email
extra_headers << ''
extra_headers << 'true' if trigger_user_email
extra_headers << 'true' if trigger_other_email
extra_headers << 'true' if trigger_auto_response_email
extra_headers << ''
end
# Fill in the blanks of the SOAP envelope with our
# session ID and the expanded XML of our request.
request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
# reset the batch size for the next request
@batch_size = DEFAULT_BATCH_SIZE
# gzip request
request = encode(request)
headers = {
'Connection' => 'Keep-Alive',
'Content-Type' => 'text/xml',
'SOAPAction' => '""',
'User-Agent' => 'activesalesforce rforce/1.0'
}
unless show_debug
headers['Accept-Encoding'] = 'gzip'
headers['Content-Encoding'] = 'gzip'
end
# Send the request to the server and read the response.
response = @server.post2(@url.path, request.lstrip, headers)
# decode if we have encoding
content = decode(response)
# Check to see if INVALID_SESSION_ID was raised and try to relogin in
if method != :login and @session_id and content =~ /sf:INVALID_SESSION_ID/
login(@user, @password)
# repackage and rencode request with the new session id
request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
request = encode(request)
# Send the request to the server and read the response.
response = @server.post2(@url.path, request.lstrip, headers)
content = decode(response)
end
SoapResponse.new(content).parse
end
# decode gzip
def decode(response)
encoding = response['Content-Encoding']
# return body if no encoding
if !encoding then return response.body end
# decode gzip
case encoding.strip
when 'gzip':
begin
gzr = Zlib::GzipReader.new(StringIO.new(response.body))
decoded = gzr.read
ensure
gzr.close
end
decoded
else
response.body
end
end
# encode gzip
def encode(request)
return request if show_debug
begin
ostream = StringIO.new
gzw = Zlib::GzipWriter.new(ostream)
gzw.write(request)
ostream.string
ensure
gzw.close
end
end
# Turns method calls on this object into remote SOAP calls.
def method_missing(method, *args)
unless args.size == 1 && [Hash, Array].include?(args[0].class)
raise 'Expected 1 Hash or Array argument'
end
call_remote method, args[0]
end
end
end