google-ads-common 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/ChangeLog +5 -0
  2. data/README +1 -4
  3. data/Rakefile +2 -2
  4. data/lib/ads_common/api.rb +106 -16
  5. data/lib/ads_common/api_config.rb +2 -3
  6. data/lib/ads_common/auth/base_handler.rb +22 -3
  7. data/lib/ads_common/auth/client_login_handler.rb +27 -32
  8. data/lib/ads_common/auth/oauth_handler.rb +260 -0
  9. data/lib/ads_common/build/savon_abstract_generator.rb +12 -11
  10. data/lib/ads_common/build/savon_generator.rb +31 -27
  11. data/lib/ads_common/build/savon_registry.rb +46 -23
  12. data/lib/ads_common/build/savon_registry_generator.rb +23 -10
  13. data/lib/ads_common/build/savon_service_generator.rb +17 -3
  14. data/lib/ads_common/config.rb +1 -1
  15. data/lib/ads_common/credential_handler.rb +3 -7
  16. data/lib/ads_common/errors.rb +18 -6
  17. data/lib/ads_common/savon_headers/base_header_handler.rb +80 -0
  18. data/lib/ads_common/{soap4r_logger.rb → savon_headers/httpi_request_proxy.rb} +27 -20
  19. data/lib/ads_common/savon_headers/oauth_header_handler.rb +92 -0
  20. data/lib/ads_common/savon_headers/simple_header_handler.rb +17 -49
  21. data/lib/ads_common/savon_service.rb +129 -41
  22. data/test/test_savon_service.rb +9 -4
  23. metadata +39 -43
  24. data/lib/ads_common/build/rake_common.rb +0 -343
  25. data/lib/ads_common/build/soap4r_generator.rb +0 -565
  26. data/lib/ads_common/savon_headers/client_login_header_handler.rb +0 -60
  27. data/lib/ads_common/soap4r_headers/nested_header_handler.rb +0 -50
  28. data/lib/ads_common/soap4r_headers/single_header_handler.rb +0 -44
  29. data/lib/ads_common/soap4r_patches.rb +0 -210
  30. data/lib/ads_common/soap4r_response_handler.rb +0 -80
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Authors:: api.sgomes@gmail.com (Sérgio Gomes)
4
4
  #
5
- # Copyright:: Copyright 2010, Google Inc. All Rights Reserved.
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
6
  #
7
7
  # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
@@ -21,17 +21,13 @@
21
21
 
22
22
  module AdsCommon
23
23
  class CredentialHandler
24
+ attr_reader :credentials
24
25
 
25
26
  def initialize(config)
26
27
  @config = config
27
28
  load_from_config
28
29
  end
29
30
 
30
- # Retrieve all credentials (optional parameter specifying API version).
31
- def credentials(version = nil)
32
- return @credentials
33
- end
34
-
35
31
  # Set the credentials hash to a new one. Calculate difference, and call the
36
32
  # AuthHandler callback appropriately.
37
33
  def credentials=(new_credentials)
@@ -59,7 +55,7 @@ module AdsCommon
59
55
  # appropriately.
60
56
  def set_credential(credential, value)
61
57
  @credentials[credential] = value
62
- @auth_handler.property_changed(credential, value) if auth_handler
58
+ @auth_handler.property_changed(credential, value) if @auth_handler
63
59
  end
64
60
 
65
61
  private
@@ -27,9 +27,25 @@ module AdsCommon
27
27
  class Error < ::StandardError
28
28
  end
29
29
 
30
- # Raised if an attempt is made to authenticate via the ClientLogin API with
31
- # missing or wrong information
30
+ # Raised if an attempt is made to authenticate with missing or wrong
31
+ # information.
32
32
  class AuthError < Error
33
+ attr_reader :error
34
+ attr_reader :info
35
+ def initialize(message = self.class.to_s, error = nil, info = nil)
36
+ super(message)
37
+ @error = error
38
+ @info = info
39
+ end
40
+ end
41
+
42
+ # Raised when OAuth access token is required.
43
+ class OAuthVerificationRequired < AuthError
44
+ attr_reader :oauth_url
45
+ def initialize(oauth_url)
46
+ super()
47
+ @oauth_url = oauth_url
48
+ end
33
49
  end
34
50
 
35
51
  # Raised if setting a non-existant property on an object
@@ -57,14 +73,10 @@ module AdsCommon
57
73
  # Superclass for API exceptions. Each client library should implement its
58
74
  # own subclass with extra fields.
59
75
  class ApiException < Error
60
-
61
76
  attr_reader :message
62
-
63
77
  def initialize(message = nil)
64
78
  @message = message
65
79
  end
66
-
67
80
  end
68
-
69
81
  end
70
82
  end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Authors:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Base class for handlers of SOAP headers.
21
+
22
+ module AdsCommon
23
+ module SavonHeaders
24
+ class BaseHeaderHandler
25
+ # Default namespace alias.
26
+ DEFAULT_NAMESPACE = 'wsdl'
27
+
28
+ # Initializes a header handler.
29
+ #
30
+ # Args:
31
+ # - credential_handler: a header with credential data
32
+ # - auth_handler: a header with auth data
33
+ # - element_name: an API-specific name of header element
34
+ # - namespace: default namespace to use
35
+ # - version: services version
36
+ #
37
+ def initialize(credential_handler, auth_handler, element_name,
38
+ namespace, version)
39
+ @credential_handler = credential_handler
40
+ @auth_handler = auth_handler
41
+ @element_name = element_name
42
+ @namespace = namespace
43
+ @version = version
44
+ Savon.configure {|config| config.raise_errors = false}
45
+ end
46
+
47
+ # Enriches soap object with API-specific headers like namespaces, login
48
+ # credentials etc.
49
+ #
50
+ # Needs to be overriden.
51
+ #
52
+ # Args:
53
+ # - request: a HTTPI Request for extra configuration
54
+ # - soap: a Savon soap object to fill fields in
55
+ # - args: request parameters to adjust for namespaces
56
+ #
57
+ # Returns:
58
+ # - Modified request and soap structures
59
+ #
60
+ def prepare_request(request, soap, args)
61
+ soap.namespace = @namespace
62
+ soap.body = args if args
63
+ # Sets the default namespace for the body.
64
+ soap.input[2] = {:xmlns => @namespace}
65
+ end
66
+
67
+ # Adds namespace to the given string.
68
+ #
69
+ # Args:
70
+ # - str: String to prepend with a namespace
71
+ #
72
+ # Returns:
73
+ # - String with a namespace
74
+ #
75
+ def prepend_namespace(str)
76
+ return "%s:%s" % [DEFAULT_NAMESPACE, str]
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,6 +1,4 @@
1
- #!/usr/bin/ruby
2
- #
3
- # Author:: api.dklimkin@gmail.com (Danial Klimkin)
1
+ # Authors:: api.dklimkin@gmail.com (Danial Klimkin)
4
2
  #
5
3
  # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
4
  #
@@ -17,26 +15,35 @@
17
15
  # See the License for the specific language governing permissions and
18
16
  # limitations under the License.
19
17
  #
20
- # Wrapper class to handle Soap4r-specific logging to standard logger.
18
+ # OAuth request proxy for HTTPI::Request.
21
19
 
22
- require 'logger'
20
+ require 'oauth/request_proxy/base'
21
+ require 'httpi/request'
23
22
 
24
- module AdsCommon
25
- class Soap4rLogger
26
- # Constructor for Soap4rLogger.
27
- #
28
- # Args:
29
- # - logger: a ruby logger to log to.
30
- # - log_level: default log_level for streams, defaults to INFO.
31
- #
32
- def initialize(logger, log_level = Logger::INFO)
33
- @logger = logger
34
- @log_level = log_level
35
- end
23
+ module OAuth
24
+ module RequestProxy
25
+ class HTTPIRequest < OAuth::RequestProxy::Base
26
+ proxies HTTPI::Request
27
+
28
+ # HTTP method to use.
29
+ def method
30
+ return 'POST'
31
+ end
32
+
33
+ # Request URL.
34
+ def uri
35
+ request.url.to_s
36
+ end
37
+
38
+ # Query parameters.
39
+ def parameters
40
+ options[:parameters]
41
+ end
36
42
 
37
- # Overload << operator to perform logging.
38
- def << (text)
39
- @logger.add(@log_level) {text.to_s}
43
+ # Request body.
44
+ def body
45
+ request.body
46
+ end
40
47
  end
41
48
  end
42
49
  end
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Authors:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Handles SOAP headers and namespaces definition for OAuth type header.
21
+
22
+ require 'oauth'
23
+
24
+ require 'ads_common/savon_headers/base_header_handler'
25
+ require 'ads_common/savon_headers/httpi_request_proxy'
26
+
27
+ module AdsCommon
28
+ module SavonHeaders
29
+ class OAuthHeaderHandler < BaseHeaderHandler
30
+ # Enriches soap object with API-specific headers like namespaces, login
31
+ # credentials etc. Sets the default namespace for the body to the one
32
+ # specified in initializer.
33
+ #
34
+ # Args:
35
+ # - request: a HTTPI Request for extra configuration
36
+ # - soap: a Savon soap object to fill fields in
37
+ # - args: request parameters to adjust for namespaces
38
+ #
39
+ # Returns:
40
+ # - Modified soap structure
41
+ #
42
+ def prepare_request(request, soap, args)
43
+ super(request, soap, args)
44
+ generate_headers(request, soap)
45
+ end
46
+
47
+ private
48
+
49
+ # Generates SOAP request header with login credentials and namespace
50
+ # definition for OAuth authentication.
51
+ #
52
+ # Args:
53
+ # - request: a HTTPI Request to generate headers for
54
+ # - soap: a Savon soap object to fill fields in
55
+ #
56
+ # Returns:
57
+ # - Hash containing a header with filled in credentials
58
+ #
59
+ def generate_headers(request, soap)
60
+ headers = @auth_handler.headers(@credential_handler.credentials)
61
+ request_header = headers.inject({}) do |request_header, (header, value)|
62
+ if header == :access_token
63
+ request.headers['Authorization'] =
64
+ generate_oauth_parameters_string(request, value, soap.endpoint)
65
+ else
66
+ request_header[prepend_namespace(header)] = value
67
+ end
68
+ request_header
69
+ end
70
+ soap.header[prepend_namespace(@element_name)] = request_header
71
+ end
72
+
73
+ # Generates auth string for OAuth method of authentication.
74
+ #
75
+ # Args:
76
+ # - request: a HTTPI::Request to sign
77
+ # - access_token: an initialized OAuth AccessToken
78
+ # - url: request URL to generate auth string for
79
+ #
80
+ # Returns:
81
+ # - Authentication string
82
+ #
83
+ def generate_oauth_parameters_string(request, access_token, url)
84
+ request.url = url
85
+ oauth_params = {:consumer => @auth_handler.get_oauth_consumer(),
86
+ :token => access_token}
87
+ oauth_helper = OAuth::Client::Helper.new(request, oauth_params)
88
+ return oauth_helper.header
89
+ end
90
+ end
91
+ end
92
+ end
@@ -17,49 +17,29 @@
17
17
  # See the License for the specific language governing permissions and
18
18
  # limitations under the License.
19
19
  #
20
- # Handles SOAP headers and namespaces definition for Savon SOAP backend.
20
+ # Handles simple SOAP headers and namespaces definition for Savon SOAP backend.
21
+
22
+ require 'ads_common/savon_headers/base_header_handler'
21
23
 
22
24
  module AdsCommon
23
25
  module SavonHeaders
24
- class SimpleHeaderHandler
25
-
26
- # Default namespace alias
27
- DEFAULT_NAMESPACE = 'wsdl'
28
-
29
- # Initializes a header handler.
30
- #
31
- # Args:
32
- # - credential_handler: a header with credential data.
33
- # - auth_handler: a header with auth data.
34
- # - element_name: an API-specific name of header element.
35
- # - namespace: default namespace to use.
36
- # - version: services version.
37
- def initialize(credential_handler, auth_handler, element_name,
38
- namespace, version)
39
- @credential_handler = credential_handler
40
- @auth_handler = auth_handler
41
- @element_name = element_name
42
- @namespace = namespace
43
- @version = version
44
- Savon.configure {|config| config.raise_errors = false}
45
- end
46
-
26
+ class SimpleHeaderHandler < BaseHeaderHandler
47
27
  # Enriches soap object with API-specific headers like namespaces, login
48
28
  # credentials etc. Sets the default namespace for the body to the one
49
29
  # specified in initializer.
50
30
  #
51
31
  # Args:
52
- # - soap: a Savon soap object to fill fields in.
53
- # - args: request parameters to adjust for namespaces.
32
+ # - request: a HTTPI Request for extra configuration (unused)
33
+ # - soap: a Savon soap object to fill fields in
34
+ # - args: request parameters to adjust for namespaces
54
35
  #
55
36
  # Returns:
56
- # - Modified soap structure.
57
- def prepare_soap(soap, args)
37
+ # - Modified soap structure
38
+ #
39
+ def prepare_request(request, soap, args)
40
+ super(request, soap, args)
58
41
  soap.header[prepend_namespace(@element_name)] =
59
42
  generate_request_header()
60
- soap.namespace = @namespace
61
- soap.body = args if args
62
- soap.input[1] = {:xmlns => @namespace}
63
43
  end
64
44
 
65
45
  private
@@ -68,29 +48,17 @@ module AdsCommon
68
48
  # definition.
69
49
  #
70
50
  # Args:
71
- # - None.
51
+ # - None
72
52
  #
73
53
  # Returns:
74
- # - Hash containing a header with filled in credentials.
54
+ # - Hash containing a header with filled in credentials
55
+ #
75
56
  def generate_request_header()
76
- request_header = {}
77
- credentials = @credential_handler.credentials(@version)
78
- headers = @auth_handler.headers(credentials)
79
- headers.each do |header, value|
57
+ headers = @auth_handler.headers(@credential_handler.credentials)
58
+ return headers.inject({}) do |request_header, (header, value)|
80
59
  request_header[prepend_namespace(header)] = value
60
+ request_header
81
61
  end
82
- return request_header
83
- end
84
-
85
- # Adds namespace to the given string.
86
- #
87
- # Args:
88
- # - str: String to prepend with a namespace.
89
- #
90
- # Returns:
91
- # - String with a namespace.
92
- def prepend_namespace(str)
93
- return "%s:%s" % [DEFAULT_NAMESPACE, str]
94
62
  end
95
63
  end
96
64
  end
@@ -19,18 +19,26 @@
19
19
  #
20
20
  # Base class for all generated API services based on Savon backend.
21
21
 
22
- gem 'savon', '~>0.9.1'
23
22
  require 'savon'
24
23
 
25
24
  module AdsCommon
26
25
  class SavonService
26
+ # Default namespace name.
27
+ DEFAULT_NAMESPACE = 'wsdl'
28
+
27
29
  attr_accessor :headerhandler
30
+ attr_reader :api
31
+ attr_reader :version
32
+ attr_reader :namespace
28
33
 
29
34
  # Creates a new service.
30
- def initialize(endpoint, namespace)
35
+ def initialize(api, endpoint, namespace, version)
31
36
  if self.class() == AdsCommon::SavonService
32
37
  raise NoMethodError, "Tried to instantiate an abstract class"
33
38
  end
39
+ @api = api
40
+ @version = version
41
+ @namespace = namespace
34
42
  @headerhandler = []
35
43
  @client = Savon::Client.new do |wsdl|
36
44
  wsdl.namespace = namespace
@@ -74,34 +82,60 @@ module AdsCommon
74
82
  # - resolve xsi:type where required;
75
83
  # - convert some native types to xml.
76
84
  def validate_args(action_name, args)
77
- validated_args = Hash.new
85
+ validated_args = {}
78
86
  in_params = get_service_registry.get_method_signature(action_name)[:input]
79
87
  in_params.each_with_index do |in_param, index|
80
88
  key = in_param[:name]
81
89
  value = deep_copy(args[index])
82
- validated_args[key] = (value.nil?) ? nil :
83
- validate_arg(value, validated_args, key)
90
+ validated_args[key] = (value.nil?) ?
91
+ nil : validate_arg(value, validated_args, key, in_param[:type])
84
92
  end
85
93
  return validated_args
86
94
  end
87
95
 
88
96
  # Validates method argument. Runs recursively if hash or array encountered.
89
97
  # Also handles some types that need special conversions.
90
- def validate_arg(arg, parent = nil, key = nil)
98
+ def validate_arg(arg, parent = nil, key = nil, field_type_name = nil)
99
+ field_type = get_full_type_signature(field_type_name)
91
100
  result = case arg
92
- when Hash then validate_hash_arg(arg, parent, key)
93
- when Array then arg.map {|item| validate_arg(item, parent, key)}
101
+ when Hash
102
+ validate_hash_arg(arg, parent, key, field_type)
103
+ when Array then arg.map do |item|
104
+ validate_arg(item, parent, key, field_type_name)
105
+ end
94
106
  when Time then time_to_xml_hash(arg)
95
107
  else arg
96
108
  end
97
109
  return result
98
110
  end
99
111
 
112
+ # Generates order of XML elements for SOAP request. Returns only items
113
+ # existing in arg.
114
+ def generate_order_for_args(arg, fields)
115
+ all_keys = fields.map {|field| field[:name]}
116
+ return all_keys & arg.keys
117
+ end
118
+
100
119
  # Validates hash argument recursively. Keeps tracking of correct place
101
120
  # for xsi:type and adds is when required.
102
- def validate_hash_arg(arg, parent = nil, key = nil)
121
+ def validate_hash_arg(arg, parent = nil, key = nil, field_type = nil)
122
+ # Non-default namespace should be used, overriding default.
123
+ if field_type and field_type.include?(:ns)
124
+ namespace = get_service_registry.get_namespace(field_type[:ns])
125
+ add_attribute(parent, prefix_key(key), 'xmlns', namespace)
126
+ end
127
+
128
+ # Handling custom xsi:type.
103
129
  xsi_type = arg.delete('xsi:type') || arg.delete(:xsi_type)
104
130
  if xsi_type
131
+ xsi_field_type = get_full_type_signature(xsi_type)
132
+ if xsi_field_type.nil?
133
+ raise AdsCommon::Errors::ApiException.new(
134
+ "Incorrect xsi:type specified: %s" % [xsi_type])
135
+ else
136
+ # TODO: make sure xsi_type is derived from field_type.
137
+ field_type = xsi_field_type
138
+ end
105
139
  if parent and key
106
140
  add_xsi_type(parent, key, xsi_type)
107
141
  else
@@ -110,24 +144,71 @@ module AdsCommon
110
144
  [xsi_type, parent, key])
111
145
  end
112
146
  end
113
- return arg.inject({}) do |result, (key, value)|
114
- result[key] = (key == :attributes!) ? value :
115
- validate_arg(value, result, key)
147
+
148
+ # Adding :order! key to keep correct order in SOAP elements.
149
+ if field_type and field_type.include?(:fields)
150
+ arg[:order!] =
151
+ generate_order_for_args(arg, field_type[:fields])
152
+ end
153
+
154
+ # Processing each key-value pair.
155
+ return arg.inject({}) do |result, (k, v)|
156
+ if (k == :attributes! or k == :order!)
157
+ result[k] = v
158
+ else
159
+ subfield = (field_type.nil?) ? nil :
160
+ get_field_by_name(field_type[:fields], k)
161
+ # Here we will give up if the field is unknown. For full validation
162
+ # we have to handle nil here.
163
+ subtype_name, subtype = if subfield and subfield.include?(:type)
164
+ subtype_name = subfield[:type]
165
+ subtype = (subtype_name.nil?) ? nil :
166
+ get_service_registry.get_type_signature(subtype_name)
167
+ [subtype_name, subtype]
168
+ end
169
+ # In case of non-default namespace, the children should be in
170
+ # overriden namespace but the node has to be in the default.
171
+ # We also have to fix order! list if we alter the key name.
172
+ new_key = if (subtype and subtype[:ns])
173
+ prefixed_key = prefix_key(k)
174
+ replace_item!(arg[:order!], k, prefixed_key)
175
+ prefixed_key
176
+ else
177
+ k
178
+ end
179
+ result[new_key] = validate_arg(v, result, k, subtype_name)
180
+ end
116
181
  result
117
182
  end
118
183
  end
119
184
 
185
+ # Replaces an item in an array with a different one into the same position.
186
+ def replace_item!(data, old_item, new_item)
187
+ data.map! {|item| (item == old_item) ? new_item : item}
188
+ end
189
+
120
190
  # Adds ":attributes!" record for Savon to specify xsi:type.
121
191
  def add_xsi_type(parent, key, xsi_type)
122
- parent[:attributes!] ||= {}
123
- parent[:attributes!][key] ||= {}
124
- parent[:attributes!][key]["xsi:type"] ||= []
125
- parent[:attributes!][key]["xsi:type"] << xsi_type
192
+ add_attribute(parent, key, 'xsi:type', xsi_type)
193
+ end
194
+
195
+ # Adds Savon attribute for given node, key, name and value.
196
+ def add_attribute(node, key, name, value)
197
+ node[:attributes!] ||= {}
198
+ node[:attributes!][key] ||= {}
199
+ node[:attributes!][key][name] = value
200
+ end
201
+
202
+ # Prefixes default namespace.
203
+ def prefix_key(key)
204
+ return "%s:%s" % [DEFAULT_NAMESPACE, key.to_s.lower_camelcase]
126
205
  end
127
206
 
128
207
  # Executes each handler to generate SOAP headers.
129
208
  def set_headers(soap, args)
130
- @headerhandler.each {|handler| handler.prepare_soap(soap, args)}
209
+ @headerhandler.each do |handler|
210
+ handler.prepare_request(@client.http, soap, args)
211
+ end
131
212
  end
132
213
 
133
214
  # Checks for errors in response and raises appropriate exception.
@@ -194,26 +275,22 @@ module AdsCommon
194
275
  output_data[field_sym] = normalize_type(output_data[field_sym],
195
276
  field_definition)
196
277
 
197
- sub_type = get_service_registry.get_type_signature(
198
- field_definition[:type])
199
- if sub_type
200
- sub_type[:fields] += implode_parent(sub_type)
201
- if sub_type[:fields]
202
- # go recursive
203
- sub_type[:fields].each do |sub_type_field|
204
- if output_data[field_sym].is_a?(Array)
205
- items_list = output_data[field_sym]
206
- output_data[field_sym] = Array.new
207
- items_list.each do |item|
208
- output_data[field_sym] <<
209
- normalize_output_field(item, sub_type_field,
210
- sub_type_field[:name])
211
- end
212
- else
213
- output_data[field_sym] =
214
- normalize_output_field(output_data[field_sym], sub_type_field,
278
+ sub_type = get_full_type_signature(field_definition[:type])
279
+ if sub_type and sub_type[:fields]
280
+ # go recursive
281
+ sub_type[:fields].each do |sub_type_field|
282
+ if output_data[field_sym].is_a?(Array)
283
+ items_list = output_data[field_sym]
284
+ output_data[field_sym] = []
285
+ items_list.each do |item|
286
+ output_data[field_sym] <<
287
+ normalize_output_field(item, sub_type_field,
215
288
  sub_type_field[:name])
216
289
  end
290
+ else
291
+ output_data[field_sym] =
292
+ normalize_output_field(output_data[field_sym], sub_type_field,
293
+ sub_type_field[:name])
217
294
  end
218
295
  end
219
296
  end
@@ -232,7 +309,8 @@ module AdsCommon
232
309
  # If field signature allows an array, forcing array structure even for one
233
310
  # item.
234
311
  if !field[:min_occurs].nil? and
235
- (field[:max_occurs].nil? or field[:max_occurs] > 1)
312
+ (field[:max_occurs] == :unbounded ||
313
+ (!field[:max_occurs].nil? and field[:max_occurs] > 1))
236
314
  result = arrayize(result)
237
315
  end
238
316
  return result
@@ -259,7 +337,7 @@ module AdsCommon
259
337
 
260
338
  # Makes sure object is an array.
261
339
  def arrayize(object)
262
- return Array.new if object.nil?
340
+ return [] if object.nil?
263
341
  return object.is_a?(Array) ? object : [object]
264
342
  end
265
343
 
@@ -273,12 +351,12 @@ module AdsCommon
273
351
 
274
352
  # Returns all inherited fields of superclasses for given type.
275
353
  def implode_parent(data_type)
276
- result = Array.new
277
- if data_type and data_type[:base]
354
+ result = []
355
+ if data_type[:base]
278
356
  parent_type = get_service_registry.get_type_signature(data_type[:base])
279
- result += parent_type[:fields] if parent_type[:fields]
280
- result += implode_parent(parent_type) if parent_type[:base]
357
+ result += implode_parent(parent_type)
281
358
  end
359
+ result += data_type[:fields]
282
360
  return result
283
361
  end
284
362
 
@@ -286,5 +364,15 @@ module AdsCommon
286
364
  def deep_copy(data)
287
365
  return Marshal.load(Marshal.dump(data))
288
366
  end
367
+
368
+ # Returns type signature with all inherited fields.
369
+ def get_full_type_signature(type_name)
370
+ result = (type_name.nil?) ? nil :
371
+ get_service_registry.get_type_signature(type_name)
372
+ if result and result[:base]
373
+ result[:fields] = implode_parent(result)
374
+ end
375
+ return result
376
+ end
289
377
  end
290
378
  end