safrano 0.3.4 → 0.4.4

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Dir/iter.rb +18 -0
  3. data/lib/core_ext/Hash/transform.rb +21 -0
  4. data/lib/core_ext/Integer/edm.rb +13 -0
  5. data/lib/core_ext/REXML/Document/output.rb +16 -0
  6. data/lib/core_ext/String/convert.rb +25 -0
  7. data/lib/core_ext/String/edm.rb +13 -0
  8. data/lib/core_ext/dir.rb +3 -0
  9. data/lib/core_ext/hash.rb +3 -0
  10. data/lib/core_ext/integer.rb +3 -0
  11. data/lib/core_ext/rexml.rb +3 -0
  12. data/lib/core_ext/string.rb +5 -0
  13. data/lib/odata/attribute.rb +15 -10
  14. data/lib/odata/batch.rb +17 -15
  15. data/lib/odata/collection.rb +141 -500
  16. data/lib/odata/collection_filter.rb +44 -37
  17. data/lib/odata/collection_media.rb +193 -43
  18. data/lib/odata/collection_order.rb +50 -37
  19. data/lib/odata/common_logger.rb +39 -12
  20. data/lib/odata/complex_type.rb +152 -0
  21. data/lib/odata/edm/primitive_types.rb +184 -0
  22. data/lib/odata/entity.rb +201 -176
  23. data/lib/odata/error.rb +186 -33
  24. data/lib/odata/expand.rb +126 -0
  25. data/lib/odata/filter/base.rb +69 -0
  26. data/lib/odata/filter/error.rb +55 -6
  27. data/lib/odata/filter/parse.rb +38 -36
  28. data/lib/odata/filter/sequel.rb +121 -67
  29. data/lib/odata/filter/sequel_function_adapter.rb +148 -0
  30. data/lib/odata/filter/token.rb +15 -11
  31. data/lib/odata/filter/tree.rb +110 -60
  32. data/lib/odata/function_import.rb +166 -0
  33. data/lib/odata/model_ext.rb +618 -0
  34. data/lib/odata/navigation_attribute.rb +50 -32
  35. data/lib/odata/relations.rb +7 -7
  36. data/lib/odata/select.rb +54 -0
  37. data/lib/{safrano_core.rb → odata/transition.rb} +14 -60
  38. data/lib/odata/url_parameters.rb +128 -37
  39. data/lib/odata/walker.rb +19 -11
  40. data/lib/safrano.rb +18 -28
  41. data/lib/safrano/contract.rb +143 -0
  42. data/lib/safrano/core.rb +43 -0
  43. data/lib/safrano/core_ext.rb +13 -0
  44. data/lib/safrano/deprecation.rb +73 -0
  45. data/lib/{multipart.rb → safrano/multipart.rb} +37 -41
  46. data/lib/safrano/rack_app.rb +175 -0
  47. data/lib/{odata_rack_builder.rb → safrano/rack_builder.rb} +18 -2
  48. data/lib/{request.rb → safrano/request.rb} +102 -50
  49. data/lib/{response.rb → safrano/response.rb} +5 -4
  50. data/lib/safrano/sequel_join_by_paths.rb +5 -0
  51. data/lib/{service.rb → safrano/service.rb} +257 -188
  52. data/lib/safrano/version.rb +5 -0
  53. data/lib/sequel/plugins/join_by_paths.rb +17 -29
  54. metadata +53 -17
  55. data/lib/rack_app.rb +0 -174
  56. data/lib/sequel_join_by_paths.rb +0 -5
  57. data/lib/version.rb +0 -4
@@ -1,137 +1,92 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'rexml/document'
4
- require 'odata/relations.rb'
5
- require 'odata/batch.rb'
6
- require 'odata/error.rb'
7
-
8
- module OData
4
+ require 'odata/relations'
5
+ require 'odata/batch'
6
+ require 'odata/complex_type'
7
+ require 'odata/function_import'
8
+ require 'odata/error'
9
+ require 'odata/filter/sequel'
10
+ require 'set'
11
+ require 'odata/collection'
12
+
13
+ module Safrano
9
14
  # this module has all methods related to expand/defered output preparation
10
15
  # and will be included in Service class
11
16
  module ExpandHandler
12
- PATH_SPLITTER = %r{\A(\w+)\/?(.*)\z}.freeze
13
- def get_deferred_odata_h(entity:, attrib:, uribase:)
14
- { '__deferred' => { 'uri' => "#{entity.uri(uribase)}/#{attrib}" } }
15
- end
16
-
17
- # split expand argument into first and rest part
18
- def split_entity_expand_arg(exp_one)
19
- if exp_one.include?('/')
20
- m = PATH_SPLITTER.match(exp_one)
21
- cur_exp = m[1].strip
22
- rest_exp = m[2]
23
- # TODO: check errorhandling
24
- raise OData::ServerError if cur_exp.nil?
17
+ PATH_SPLITTER = %r{\A(\w+)/?(.*)\z}.freeze
18
+ DEFERRED = '__deferred'.freeze
19
+ URI = 'uri'.freeze
25
20
 
26
- k = cur_exp.to_sym
27
- else
28
- k = exp_one.strip.to_sym
29
- rest_exp = nil
30
- end
31
- yield k, rest_exp
21
+ def get_deferred_odata_h(entity_uri:, attrib:)
22
+ { DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
32
23
  end
33
24
 
34
25
  # default v2
35
26
  # overriden in ServiceV1
36
- def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
37
- res = array.map do |w|
38
- get_entity_odata_h(entity: w,
39
- expand: expand,
40
- uribase: uribase)
41
- end
42
- if icount
43
- { 'results' => res, '__count' => icount }
44
- else
45
- { 'results' => res }
46
- end
47
- end
48
-
49
- # handle a single expand
50
- def handle_entity_expand_one(entity:, exp_one:, nav_values_h:, nav_coll_h:,
51
- uribase:)
52
-
53
- split_entity_expand_arg(exp_one) do |first, rest_exp|
54
- if (enval = entity.nav_values[first])
55
- nav_values_h[first.to_s] = get_entity_odata_h(entity: enval,
56
- expand: rest_exp,
57
- uribase: uribase)
58
- elsif (encoll = entity.nav_coll[first])
59
- # nav attributes that are a collection (x..n)
60
- nav_coll_h[first.to_s] = get_coll_odata_h(array: encoll,
61
- expand: rest_exp,
62
- uribase: uribase)
63
-
64
- end
65
- end
66
- end
67
-
68
- def handle_entity_expand(entity:, expand:, nav_values_h:,
69
- nav_coll_h:, uribase:)
70
- expand.strip!
71
- explist = expand.split(',')
72
- # handle multiple expands
73
- explist.each do |exp|
74
- handle_entity_expand_one(entity: entity, exp_one: exp,
75
- nav_values_h: nav_values_h,
76
- nav_coll_h: nav_coll_h,
77
- uribase: uribase)
78
- end
79
- end
80
-
81
- def handle_entity_deferred_attribs(entity:, nav_values_h:,
82
- nav_coll_h:, uribase:)
83
- entity.nav_values.each_key do |ksy|
84
- ks = ksy.to_s
85
- next if nav_values_h.key?(ks)
86
-
87
- nav_values_h[ks] = get_deferred_odata_h(entity: entity,
88
- attrib: ks, uribase: uribase)
89
- end
90
- entity.nav_coll.each_key do |ksy|
91
- ks = ksy.to_s
92
- next if nav_coll_h.key?(ks)
93
-
94
- nav_coll_h[ks] = get_deferred_odata_h(entity: entity, attrib: ks,
95
- uribase: uribase)
27
+ RESULTS_K = 'results'.freeze
28
+ COUNT_K = '__count'.freeze
29
+ def get_coll_odata_h(array:, template:, icount: nil)
30
+ array.map! do |w|
31
+ get_entity_odata_h(entity: w, template: template)
96
32
  end
33
+ icount ? { RESULTS_K => array, COUNT_K => icount } : { RESULTS_K => array }
97
34
  end
98
35
 
99
36
  # handle $links ... Note: $expand seems to be ignored when $links
100
37
  # are requested
101
- def get_entity_odata_link_h(entity:, uribase:)
102
- { uri: entity.uri(uribase) }
103
- end
38
+ def get_entity_odata_link_h(entity:)
39
+ { uri: entity.uri }
40
+ end
41
+
42
+ EMPTYH = {}.freeze
43
+ METADATA_K = '__metadata'.freeze
44
+ def get_entity_odata_h(entity:, template:)
45
+ # start with metadata
46
+ hres = { METADATA_K => entity.metadata_h }
47
+
48
+ template.each do |elmt, arg|
49
+ case elmt
50
+ when :all_values
51
+ hres.merge! entity.casted_values
52
+
53
+ when :selected_vals
54
+ hres.merge! entity.casted_values(arg)
55
+
56
+ when :expand_e
57
+
58
+ arg.each do |attr, templ|
59
+ enval = entity.send(attr)
60
+ hres[attr] = if enval
61
+ get_entity_odata_h(entity: enval, template: templ)
62
+ else
63
+ # FK is NULL --> nav_value is nil --> return empty json
64
+ EMPTYH
65
+ end
66
+ end
104
67
 
105
- def get_entity_odata_h(entity:, expand: nil, uribase:)
106
- hres = entity.casted_values
107
- hres['__metadata'] = entity.metadata_h(uribase: uribase)
68
+ when :expand_c
69
+ arg.each do |attr, templ|
70
+ next unless (encoll = entity.send(attr))
108
71
 
109
- nav_values_h = {}
110
- nav_coll_h = {}
72
+ # nav attributes that are a collection (x..n)
73
+ hres[attr] = get_coll_odata_h(array: encoll, template: templ)
74
+ # else error ?
75
+ end
111
76
 
112
- # handle expanded nav attributes
113
- unless expand.nil?
114
- handle_entity_expand(entity: entity, expand: expand,
115
- nav_values_h: nav_values_h,
116
- nav_coll_h: nav_coll_h,
117
- uribase: uribase)
77
+ when :deferr
78
+ euri = entity.uri
79
+ arg.each do |attr|
80
+ hres[attr] = get_deferred_odata_h(entity_uri: euri, attrib: attr)
81
+ end
82
+ end
118
83
  end
119
-
120
- # handle not expanded (deferred) nav attributes
121
- handle_entity_deferred_attribs(entity: entity,
122
- nav_values_h: nav_values_h,
123
- nav_coll_h: nav_coll_h,
124
- uribase: uribase)
125
- # merge ...
126
- hres.merge!(nav_values_h)
127
- hres.merge!(nav_coll_h)
128
-
129
84
  hres
130
85
  end
131
86
  end
132
87
  end
133
88
 
134
- module OData
89
+ module Safrano
135
90
  # xml namespace constants needed for the output of XML service and
136
91
  # and metadata
137
92
  module XMLNS
@@ -147,17 +102,18 @@ module OData
147
102
  end
148
103
 
149
104
  # Link to Model
150
- module OData
105
+ module Safrano
151
106
  MAX_DATASERVICE_VERSION = '2'.freeze
152
107
  MIN_DATASERVICE_VERSION = '1'.freeze
108
+ CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
109
+ CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
153
110
  include XMLNS
154
111
  # Base class for service. Subclass will be for V1, V2 etc...
155
112
  class ServiceBase
156
113
  include Safrano
157
114
  include ExpandHandler
158
115
 
159
- XML_PREAMBLE = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>' + \
160
- "\r\n".freeze
116
+ XML_PREAMBLE = %Q(<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n).freeze
161
117
 
162
118
  # This is just a hash of entity Set Names to the corresponding Class
163
119
  # Example
@@ -175,10 +131,14 @@ module OData
175
131
  attr_accessor :xname
176
132
  attr_accessor :xnamespace
177
133
  attr_accessor :xpath_prefix
178
- # attr_accessor :xuribase
134
+ attr_accessor :xserver_url
135
+ attr_accessor :uribase
179
136
  attr_accessor :meta
180
137
  attr_accessor :batch_handler
181
138
  attr_accessor :relman
139
+ attr_accessor :complex_types
140
+ attr_accessor :function_imports
141
+
182
142
  # Instance attributes for specialized Version specific Instances
183
143
  attr_accessor :v1
184
144
  attr_accessor :v2
@@ -192,36 +152,31 @@ module OData
192
152
  # because of the version subclasses that dont use "super" initialise
193
153
  # (todo: why not??)
194
154
  @meta = ServiceMeta.new(self)
195
- @batch_handler = OData::Batch::DisabledHandler.new
196
- @relman = OData::RelationManager.new
155
+ @batch_handler = Safrano::Batch::DisabledHandler.new
156
+ @relman = Safrano::RelationManager.new
157
+ @complex_types = Set.new
158
+ @function_imports = {}
197
159
  @cmap = {}
198
160
  instance_eval(&block) if block_given?
199
161
  end
200
162
 
201
- DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
202
163
  TRAILING_SLASH = %r{/\z}.freeze
203
- DEFAULT_PATH_PREFIX = '/'
204
-
205
- # input is the DataServiceVersion request header string, eg.
206
- # '2.0;blabla' ---> Version -> 2
207
- def self.parse_data_service_version(inp)
208
- m = DATASERVICEVERSION_RGX.match(inp)
209
- m[1] if m
210
- end
164
+ DEFAULT_PATH_PREFIX = '/'.freeze
165
+ DEFAULT_SERVER_URL = 'http://localhost:9494'.freeze
211
166
 
212
167
  def enable_batch
213
- @batch_handler = OData::Batch::EnabledHandler.new
168
+ @batch_handler = Safrano::Batch::EnabledHandler.new
214
169
  (@v1.batch_handler = @batch_handler) if @v1
215
170
  (@v2.batch_handler = @batch_handler) if @v2
216
171
  end
217
172
 
218
173
  def enable_v1_service
219
- @v1 = OData::ServiceV1.new
174
+ @v1 = Safrano::ServiceV1.new
220
175
  copy_attribs_to @v1
221
176
  end
222
177
 
223
178
  def enable_v2_service
224
- @v2 = OData::ServiceV2.new
179
+ @v2 = Safrano::ServiceV2.new
225
180
  copy_attribs_to @v2
226
181
  end
227
182
 
@@ -243,59 +198,91 @@ module OData
243
198
  (@v1.xpath_prefix = @xpath_prefix) if @v1
244
199
  (@v2.xpath_prefix = @xpath_prefix) if @v2
245
200
  end
201
+
202
+ def server_url(surl)
203
+ @xserver_url = surl.sub(TRAILING_SLASH, '')
204
+ (@v1.xserver_url = @xserver_url) if @v1
205
+ (@v2.xserver_url = @xserver_url) if @v2
206
+ end
207
+
246
208
  # end public API
247
209
 
210
+ def set_uribase
211
+ @uribase = if @xpath_prefix.empty?
212
+ @xserver_url
213
+ elsif @xpath_prefix[0] == '/'
214
+ "#{@xserver_url}#{@xpath_prefix}"
215
+ else
216
+ "#{@xserver_url}/#{@xpath_prefix}"
217
+ end
218
+ (@v1.uribase = @uribase) if @v1
219
+ (@v2.uribase = @uribase) if @v2
220
+ end
221
+
248
222
  def copy_attribs_to(other)
249
223
  other.cmap = @cmap
250
224
  other.collections = @collections
225
+ other.allowed_transitions = @allowed_transitions
251
226
  other.xtitle = @xtitle
252
227
  other.xname = @xname
253
228
  other.xnamespace = @xnamespace
254
229
  other.xpath_prefix = @xpath_prefix
230
+ other.xserver_url = @xserver_url
231
+ other.uribase = @uribase
255
232
  other.meta = ServiceMeta.new(other) # hum ... #todo: versions as well ?
256
233
  other.relman = @relman
257
234
  other.batch_handler = @batch_handler
235
+ other.complex_types = @complex_types
236
+ other.function_imports = @function_imports
258
237
  other
259
238
  end
260
239
 
240
+ # this is a central place. We extend Sequel models with OData functionality
241
+ # The included/extended modules depends on the properties(eg, pks, field types) of the model
242
+ # we differentiate
243
+ # * Single/Multi PK
244
+ # * Media/Non-Media entity
245
+ # Putting this logic here in modules loaded once on start shall result in less runtime overhead
261
246
  def register_model(modelklass, entity_set_name = nil, is_media = false)
262
247
  # check that the provided klass is a Sequel Model
263
- unless modelklass.is_a? Sequel::Model::ClassMethods
264
- raise OData::API::ModelNameError, modelklass
265
- end
266
248
 
267
- if modelklass.ancestors.include? OData::Entity
249
+ raise(Safrano::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
250
+
251
+ if modelklass.ancestors.include? Safrano::Entity
268
252
  # modules were already added previously;
269
253
  # cleanup state to avoid having data from previous calls
270
254
  # mostly usefull for testing (eg API)
271
255
  modelklass.reset
272
- else # first API call... (normal non-testing case)
273
- if modelklass.primary_key.is_a?(Array)
274
- modelklass.extend OData::EntityClassMultiPK
275
- modelklass.include OData::EntityMultiPK
276
- else
277
- modelklass.extend OData::EntityClassSinglePK
278
- modelklass.include OData::EntitySinglePK
279
- end
256
+ elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
257
+ modelklass.extend Safrano::EntityClassMultiPK
258
+ modelklass.include Safrano::EntityMultiPK
259
+ else
260
+ modelklass.extend Safrano::EntityClassSinglePK
261
+ modelklass.include Safrano::EntitySinglePK
280
262
  end
263
+
281
264
  # Media/Non-media
282
265
  if is_media
283
- modelklass.extend OData::EntityClassMedia
266
+ modelklass.extend Safrano::EntityClassMedia
284
267
  # set default media handler . Can be overridden later with the
285
268
  # "use HandlerKlass, options" API
286
269
 
287
270
  modelklass.set_default_media_handler
288
271
  modelklass.api_check_media_fields
289
- modelklass.include OData::MediaEntity
272
+ modelklass.include Safrano::MediaEntity
290
273
  else
291
- modelklass.extend OData::EntityClassNonMedia
292
- modelklass.include OData::NonMediaEntity
274
+ modelklass.extend Safrano::EntityClassNonMedia
275
+ modelklass.include Safrano::NonMediaEntity
293
276
  end
294
277
 
295
278
  modelklass.prepare_pk
296
279
  modelklass.prepare_fields
297
280
  esname = (entity_set_name || modelklass).to_s.freeze
298
- modelklass.instance_eval { @entity_set_name = esname }
281
+ serv_namespace = @xnamespace
282
+ modelklass.instance_eval do
283
+ @entity_set_name = esname
284
+ @namespace = serv_namespace
285
+ end
299
286
  @cmap[esname] = modelklass
300
287
  set_collections_sorted(@cmap.values)
301
288
  end
@@ -316,6 +303,23 @@ module OData
316
303
  modelklass.deferred_iblock = block if block_given?
317
304
  end
318
305
 
306
+ def publish_complex_type(ctklass)
307
+ # check that the provided klass is a Safrano ComplexType
308
+
309
+ raise(Safrano::API::ComplexTypeNameError, ctklass) unless ctklass.superclass == Safrano::ComplexType
310
+
311
+ serv_namespace = @xnamespace
312
+ ctklass.instance_eval { @namespace = serv_namespace }
313
+
314
+ @complex_types.add ctklass
315
+ end
316
+
317
+ def function_import(name)
318
+ funcimp = Safrano::FunctionImport(name)
319
+ @function_imports[name] = funcimp
320
+ funcimp
321
+ end
322
+
319
323
  def cmap=(imap)
320
324
  @cmap = imap
321
325
  set_collections_sorted(@cmap.values)
@@ -326,15 +330,14 @@ module OData
326
330
  # example: CrewMember must be matched before Crew otherwise we get error
327
331
  def set_collections_sorted(coll_data)
328
332
  @collections = coll_data
329
- if @collections
330
- @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse!
331
- end
333
+ @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
332
334
  @collections
333
335
  end
334
336
 
335
337
  # to be called at end of publishing block to ensure we get the right names
336
338
  # and additionally build the list of valid attribute path's used
337
339
  # for validation of $orderby or $filter params
340
+
338
341
  def finalize_publishing
339
342
  # build the cmap
340
343
  @cmap = {}
@@ -348,8 +351,32 @@ module OData
348
351
  # set default path prefix if path_prefix was not called
349
352
  path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
350
353
 
351
- # and finally build the path list
352
- @collections.each(&:build_attribute_path_list)
354
+ # set default server url if server_url was not called
355
+ server_url(DEFAULT_SERVER_URL) unless @xserver_url
356
+
357
+ set_uribase
358
+
359
+ @collections.each(&:finalize_publishing)
360
+
361
+ # finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
362
+ @collections.each do |klass|
363
+ klass.build_uri(@uribase)
364
+ klass.include(klass.time_cols.empty? ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
365
+ end
366
+
367
+ # build allowed transitions (requires that @collections are filled and sorted for having a
368
+ # correct base_url_regexp)
369
+ build_allowed_transitions
370
+
371
+ # mixin adapter specific modules where needed
372
+ case Sequel::Model.db.adapter_scheme
373
+ when :postgres
374
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreePostgres
375
+ when :sqlite
376
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeSqlite
377
+ else
378
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeDefault
379
+ end
353
380
  end
354
381
 
355
382
  def execute_deferred_iblocks
@@ -365,19 +392,24 @@ module OData
365
392
  @collections.map(&:entity_set_name).join('|')
366
393
  end
367
394
 
395
+ def base_url_func_regexp
396
+ @function_imports.keys.join('|')
397
+ end
398
+
368
399
  def service
369
400
  hres = {}
370
401
  hres['d'] = { 'EntitySets' => @collections.map(&:type_name) }
371
402
  hres
372
403
  end
373
404
 
374
- def service_xml(req)
405
+ def service_xml(_req)
375
406
  doc = REXML::Document.new
376
407
  # separator required ? ?
377
- root = doc.add_element('service', 'xml:base' => req.uribase)
408
+ root = doc.add_element('service', 'xml:base' => @uribase)
378
409
 
379
410
  root.add_namespace('xmlns:atom', XMLNS::W3_2005_ATOM)
380
411
  root.add_namespace('xmlns:app', XMLNS::W3_2007_APP)
412
+
381
413
  # this generates the main xmlns attribute
382
414
  root.add_namespace(XMLNS::W3_2007_APP)
383
415
  wp = root.add_element 'workspace'
@@ -388,7 +420,7 @@ module OData
388
420
  @collections.each do |klass|
389
421
  col = wp.add_element('collection', 'href' => klass.entity_set_name)
390
422
  ct = col.add_element('atom:title')
391
- ct.text = klass.type_name
423
+ ct.text = klass.entity_set_name
392
424
  end
393
425
 
394
426
  XML_PREAMBLE + doc.to_pretty_xml
@@ -397,10 +429,18 @@ module OData
397
429
  def add_metadata_xml_entity_type(schema)
398
430
  @collections.each do |klass|
399
431
  enty = klass.add_metadata_rexml(schema)
400
- klass.add_metadata_navs_rexml(enty, @cmap, @relman, @xnamespace)
432
+ klass.add_metadata_navs_rexml(enty, @relman)
401
433
  end
402
434
  end
403
435
 
436
+ def add_metadata_xml_complex_types(schema)
437
+ @complex_types.each { |ctklass| ctklass.add_metadata_rexml(schema) }
438
+ end
439
+
440
+ def add_metadata_xml_function_imports(ec)
441
+ @function_imports.each_value { |func| func.add_metadata_rexml(ec) }
442
+ end
443
+
404
444
  def add_metadata_xml_associations(schema)
405
445
  @relman.each_rel do |rel|
406
446
  rel.with_metadata_info(@xnamespace) do |name, bdinfo|
@@ -423,7 +463,7 @@ module OData
423
463
  # 3.a Entity set's
424
464
  ec.add_element('EntitySet',
425
465
  'Name' => klass.entity_set_name,
426
- 'EntityType' => "#{@xnamespace}.#{klass.type_name}")
466
+ 'EntityType' => klass.type_name)
427
467
  end
428
468
  # 3.b Association set's
429
469
  @relman.each_rel do |rel|
@@ -437,6 +477,9 @@ module OData
437
477
  assoc.add_element('End', assoend)
438
478
  end
439
479
  end
480
+
481
+ # 4 function imports
482
+ add_metadata_xml_function_imports(ec)
440
483
  end
441
484
 
442
485
  def metadata_xml(_req)
@@ -470,9 +513,12 @@ module OData
470
513
  schema = serv.add_element('Schema',
471
514
  'Namespace' => @xnamespace,
472
515
  'xmlns' => XMLNS::MSFT_ADO_2009_EDM)
473
- # 1. all EntityType
516
+ # 1. a. all EntityType
474
517
  add_metadata_xml_entity_type(schema)
475
518
 
519
+ # 1. b. all ComplexType
520
+ add_metadata_xml_complex_types(schema)
521
+
476
522
  # 2. Associations
477
523
  add_metadata_xml_associations(schema)
478
524
 
@@ -485,19 +531,44 @@ module OData
485
531
  # methods related to transitions to next state (cf. walker)
486
532
  module Transitions
487
533
  DOLLAR_ID_REGEXP = Regexp.new('\A\/\$').freeze
534
+ ALLOWED_TRANSITIONS_FIXED = [
535
+ Safrano::TransitionEnd,
536
+ Safrano::TransitionMetadata,
537
+ Safrano::TransitionBatch,
538
+ Safrano::TransitionContentId
539
+ ].freeze
540
+
541
+ def build_allowed_transitions
542
+ @allowed_transitions = if @function_imports.empty?
543
+ (ALLOWED_TRANSITIONS_FIXED + [
544
+ Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
545
+ trans: 'transition_collection')
546
+ ]).freeze
547
+ else
548
+ (ALLOWED_TRANSITIONS_FIXED + [
549
+ Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
550
+ trans: 'transition_collection'),
551
+ Safrano::Transition.new(%r{\A/(#{base_url_func_regexp})(.*)},
552
+ trans: 'transition_service_op')
553
+ ]).freeze
554
+ end
555
+ end
556
+
488
557
  def allowed_transitions
489
- @allowed_transitions = [
490
- Safrano::TransitionEnd,
491
- Safrano::TransitionMetadata,
492
- Safrano::TransitionBatch,
493
- Safrano::TransitionContentId,
494
- Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
495
- trans: 'transition_collection')
496
- ]
558
+ @allowed_transitions
559
+ end
560
+
561
+ def thread_safe_collection(collklass)
562
+ Safrano::OData::Collection.new(collklass)
497
563
  end
498
564
 
499
565
  def transition_collection(match_result)
500
- [@cmap[match_result[1]], :run] if match_result[1]
566
+ [thread_safe_collection(@cmap[match_result[1]]), :run] if match_result[1]
567
+ # [@cmap[match_result[1]], :run] if match_result[1]
568
+ end
569
+
570
+ def transition_service_op(match_result)
571
+ [@function_imports[match_result[1]], :run] if match_result[1]
501
572
  end
502
573
 
503
574
  def transition_batch(_match_result)
@@ -513,7 +584,7 @@ module OData
513
584
  end
514
585
 
515
586
  def transition_end(_match_result)
516
- [nil, :end]
587
+ Safrano::Transition::RESULT_END
517
588
  end
518
589
  end
519
590
 
@@ -521,14 +592,11 @@ module OData
521
592
 
522
593
  def odata_get(req)
523
594
  if req.accept?(APPXML)
524
- # app.headers 'Content-Type' => 'application/xml;charset=utf-8'
525
- # Doc: 2.2.3.7.1 Service Document As per [RFC5023], AtomPub Service
526
- # Documents MUST be
527
- # identified with the "application/atomsvc+xml" media type (see
528
- # [RFC5023] section 8).
529
- [200, CT_ATOMXML, [service_xml(req)]]
595
+ # OData V2 reference service implementations are returning app-xml-u8
596
+ # so we do
597
+ [200, CT_APPXML, [service_xml(req)]]
530
598
  else
531
- # this is returned by http://services.odata.org/V2/OData/OData.svc
599
+ # this is returned by http://services.odata.org/V2/OData/Safrano.svc
532
600
  415
533
601
  end
534
602
  end
@@ -540,22 +608,22 @@ module OData
540
608
  @data_service_version = '1.0'
541
609
  end
542
610
 
543
- def get_coll_odata_links_h(array:, uribase:, icount: nil)
611
+ def get_coll_odata_links_h(array:, icount: nil)
544
612
  array.map do |w|
545
- get_entity_odata_link_h(entity: w, uribase: uribase)
613
+ get_entity_odata_link_h(entity: w)
546
614
  end
547
615
  end
548
616
 
549
- def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
550
- array.map do |w|
617
+ def get_coll_odata_h(array:, template:, icount: nil)
618
+ array.map! do |w|
551
619
  get_entity_odata_h(entity: w,
552
- expand: expand,
553
- uribase: uribase)
620
+ template: template)
554
621
  end
622
+ array
555
623
  end
556
624
 
557
625
  def get_emptycoll_odata_h
558
- [{}]
626
+ EMPTY_HASH_IN_ARY
559
627
  end
560
628
  end
561
629
 
@@ -565,36 +633,37 @@ module OData
565
633
  @data_service_version = '2.0'
566
634
  end
567
635
 
568
- def get_coll_odata_links_h(array:, uribase:, icount: nil)
636
+ def get_coll_odata_links_h(array:, icount: nil)
569
637
  res = array.map do |w|
570
- get_entity_odata_link_h(entity: w, uribase: uribase)
638
+ get_entity_odata_link_h(entity: w)
571
639
  end
572
640
  if icount
573
- { 'results' => res, '__count' => icount }
641
+ { RESULTS_K => res, COUNT_K => icount }
574
642
  else
575
- { 'results' => res }
643
+ { RESULTS_K => res }
576
644
  end
577
645
  end
578
646
 
579
647
  def get_emptycoll_odata_h
580
- { 'results' => [{}] }
648
+ { RESULTS_K => EMPTY_HASH_IN_ARY }
581
649
  end
582
650
  end
583
651
 
584
652
  # a virtual entity for the service metadata
585
653
  class ServiceMeta
586
654
  attr_accessor :service
655
+
587
656
  def initialize(service)
588
657
  @service = service
589
658
  end
590
659
 
660
+ ALLOWED_TRANSITIONS_FIXED = [Safrano::TransitionEnd].freeze
591
661
  def allowed_transitions
592
- @allowed_transitions = [Safrano::Transition.new('',
593
- trans: 'transition_end')]
662
+ ALLOWED_TRANSITIONS_FIXED
594
663
  end
595
664
 
596
665
  def transition_end(_match_result)
597
- [nil, :end]
666
+ Safrano::Transition::RESULT_END
598
667
  end
599
668
 
600
669
  def odata_get(req)