class AdwordsApi::BatchJobUtils

Constants

DEFAULT_HEADERS
METHODS_BY_OPERATION_TYPE
REQUIRED_CONTENT_LENGTH_INCREMENT

For incremental uploads, the size (in bytes) of the body of the request must be in multiples of 256k.

SERVICES_BY_OPERATION_TYPE
UPLOAD_XML_PREFIX
UPLOAD_XML_SUFFIX

Public Class Methods

new(api, version) click to toggle source

Default constructor.

Args:

  • api: AdwordsApi object

  • version: API version to use

# File lib/adwords_api/batch_job_utils.rb, line 36
def initialize(api, version)
  @api, @version = api, version
end

Public Instance Methods

get_job_results(batch_job_url) click to toggle source

Downloads the results of a batch job from the specified URL.

Args:

  • batch_job_url: The URL provided by BatchJobService to fetch the results from

Returns:

  • the results of the batch job, as a ruby hash, or nil if none yet exist

# File lib/adwords_api/batch_job_utils.rb, line 164
def get_job_results(batch_job_url)
  @api.utils_reporter.batch_job_utils_used()
  xml_response = AdsCommon::Http.get_response(batch_job_url, @api.config)
  begin
    return sanitize_result(
        get_nori().parse(xml_response.body)[:mutate_response][:rval])
  rescue
    return nil
  end
end
initialize_url(batch_job_url) click to toggle source

Initializes an upload URL to get the actual URL to which to upload operations.

Args:

  • batch_job_url: The UploadURL provided by BatchJobService

Returns:

  • The URL that should actually be used to upload operations.

# File lib/adwords_api/batch_job_utils.rb, line 85
def initialize_url(batch_job_url)
  headers = DEFAULT_HEADERS
  headers['Content-Length'] = 0
  headers['x-goog-resumable'] = 'start'

  response = AdsCommon::Http.post_response(
      batch_job_url, '', @api.config, headers)

  return response.headers['Location']
end
put_incremental_operations( operations, batch_job_url, total_content_length = 0, is_last_request = false) click to toggle source

Puts the provided operations to the provided URL, allowing for incremental followup puts.

Args:

  • soap_operations: An array including SOAP operations provided by generate_soap_operations

  • batch_job_url: The UploadURL provided by BatchJobService

  • total_content_length: The total number of bytes already uploaded incrementally. Set this to 0 the first time you call the method.

  • is_last_request: Whether or not this set of uploads will conclude the full request.

Returns:

  • total content length, including what was just uploaded. Pass this back into this method on subsequent calls.

# File lib/adwords_api/batch_job_utils.rb, line 111
def put_incremental_operations(
    operations, batch_job_url, total_content_length = 0,
    is_last_request = false)
  @api.utils_reporter.batch_job_utils_used()
  headers = DEFAULT_HEADERS
  soap_operations = generate_soap_operations(operations)
  request_body = soap_operations.join
  is_first_request = (total_content_length == 0)

  if is_first_request
    request_body = (UPLOAD_XML_PREFIX % [@version]) + request_body
  end
  if is_last_request
    request_body += UPLOAD_XML_SUFFIX
  end

  request_body = add_padding(request_body)
  content_length = request_body.bytesize

  headers['Content-Length'] = content_length

  lower_bound = total_content_length
  upper_bound = total_content_length + content_length - 1
  total_bytes = is_last_request ? upper_bound + 1 : '*'
  content_range =
      "bytes %d-%d/%s" % [lower_bound, upper_bound, total_bytes]

  headers['Content-Range'] = content_range

  log_request(batch_job_url, headers, request_body)

  # The HTTPI library fails to handle the response when uploading
  # incremental requests. We're not interested in the response, so just
  # ignore the error.
  begin
    AdsCommon::Http.put_response(
        batch_job_url, request_body, @api.config, headers)
  rescue ArgumentError
  end

  total_content_length += content_length
  return total_content_length
end
start_incremental_upload(batch_job_url, uploaded_bytes = 0) click to toggle source

Provides a helper to manage incremental uploads.

Args:

  • batch_job_url: The UploadURL provided by BatchJobService for new jobs,

or the upload_url from IncrementalUploadHelper for continued jobs.

  • uploaded_bytes: The number of bytes already uploaded for this

incremental batch job. Can be retrieved from the IncrementalUploadHelper using uploaded_bytes.

Returns:

keeping track of uploaded bytes automatically.

# File lib/adwords_api/batch_job_utils.rb, line 71
def start_incremental_upload(batch_job_url, uploaded_bytes = 0)
  return AdwordsApi::IncrementalUploadHelper.new(
      self, uploaded_bytes, batch_job_url)
end
upload_operations(operations, batch_job_url) click to toggle source

Uploads the given operations for a batch job to the provided URL.

Args:

  • hash_operations: An array of ruby has operations to execute by posting them to the provided URL

  • service_name: The name of the AdwordsApi service as a symbol that would normally make this request

  • batch_job_url: The UploadURL provided by BatchJobService

Raises:

  • InvalidBatchJobOperationError: If there is a problem converting the

given operations to SOAP.

# File lib/adwords_api/batch_job_utils.rb, line 53
def upload_operations(operations, batch_job_url)
  helper = start_incremental_upload(batch_job_url)
  helper.upload(operations, true)
end

Private Instance Methods

add_padding(xml) click to toggle source
# File lib/adwords_api/batch_job_utils.rb, line 311
def add_padding(xml)
  remainder = xml.bytesize % REQUIRED_CONTENT_LENGTH_INCREMENT
  return xml if remainder == 0
  bytes_to_add = REQUIRED_CONTENT_LENGTH_INCREMENT - remainder
  padded_xml = xml + (' ' * bytes_to_add)
  return padded_xml
end
check_xsi_type(xml_node) click to toggle source
# File lib/adwords_api/batch_job_utils.rb, line 267
def check_xsi_type(xml_node)
  xsi_type = xml_node['xsi:type']
  unless xsi_type.nil? or xsi_type.start_with?('ns1:')
    xml_node['xsi:type'] = 'ns1:' + xsi_type
  end
  children = xml_node.children
  unless children.empty?
    children.each do |element|
      check_xsi_type(element)
    end
  end
end
extract_soap_operations(full_soap_xml) click to toggle source

Given a full SOAP xml string, extract just the operations element from the SOAP body as a string.

# File lib/adwords_api/batch_job_utils.rb, line 257
def extract_soap_operations(full_soap_xml)
  doc = Nokogiri::XML(full_soap_xml)
  operations = doc.css('wsdl|operations')
  operations.attr('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
  operations.each do |element|
    check_xsi_type(element)
  end
  return operations.to_s
end
generate_soap_operations(hash_operations) click to toggle source
# File lib/adwords_api/batch_job_utils.rb, line 230
def generate_soap_operations(hash_operations)
  unless hash_operations.is_a?(Array)
    raise AdwordsApi::Errors::InvalidBatchJobOperationError,
        'Operations must be in an array.'
  end
  return hash_operations.map do |operation|
    operation_type = operation[:xsi_type]
    if operation_type.nil?
      raise AdwordsApi::Errors::InvalidBatchJobOperationError,
          ':xsi_type for operations must be defined ' +
          'explicitly for batch jobs.'
    end
    service_name = SERVICES_BY_OPERATION_TYPE[operation_type]
    if service_name.nil?
      raise AdwordsApi::Errors::InvalidBatchJobOperationError,
          'Unknown operation type: %s' % operation_type
    end
    method_name = METHODS_BY_OPERATION_TYPE[operation_type]
    service = @api.service(service_name, @version)
    full_soap_xml = service.send(method_name, [operation])
    operation_xml = extract_soap_operations(full_soap_xml)
    operation_xml
  end
end
get_nori() click to toggle source
# File lib/adwords_api/batch_job_utils.rb, line 319
def get_nori()
  return @nori if @nori

  nori_options = {
    :strip_namespaces      => true,
    :convert_tags_to       => lambda { |tag| tag.snakecase.to_sym },
    :empty_tag_value       => "",
    :advanced_typecasting  => false
  }

  @nori = Nori.new(nori_options)

  return @nori
end
log_request(url, headers, body) click to toggle source

Logs the request on debug level.

# File lib/adwords_api/batch_job_utils.rb, line 281
def log_request(url, headers, body)
  logger = @api.logger
  logger.debug("Report request to: '%s'" % url)
  logger.debug('HTTP headers: [%s]' %
      (headers.map { |k, v| [k, v].join(': ') }.join(', ')))
  logger.debug(body)
end
sanitize_result(results) click to toggle source

Removes extraneous XML information from return hash.

# File lib/adwords_api/batch_job_utils.rb, line 290
def sanitize_result(results)
  if results.is_a?(Array)
    ret = []
    results.each do |result|
      ret << sanitize_result(result)
    end
    return ret
  end

  if results.is_a?(Hash)
    ret = {}
    results.each do |k, v|
      v = sanitize_result(v) if v.is_a?(Hash)
      ret[k] = v unless k.to_s.start_with?('@')
    end
    return ret
  end

  return results
end