google-ads-common 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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