safrano 0.3.4 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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)