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.
- checksums.yaml +4 -4
- data/lib/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +17 -15
- data/lib/odata/collection.rb +141 -500
- data/lib/odata/collection_filter.rb +44 -37
- data/lib/odata/collection_media.rb +193 -43
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +39 -12
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +201 -176
- data/lib/odata/error.rb +186 -33
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +69 -0
- data/lib/odata/filter/error.rb +55 -6
- data/lib/odata/filter/parse.rb +38 -36
- data/lib/odata/filter/sequel.rb +121 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +15 -11
- data/lib/odata/filter/tree.rb +110 -60
- data/lib/odata/function_import.rb +166 -0
- data/lib/odata/model_ext.rb +618 -0
- data/lib/odata/navigation_attribute.rb +50 -32
- data/lib/odata/relations.rb +7 -7
- data/lib/odata/select.rb +54 -0
- data/lib/{safrano_core.rb → odata/transition.rb} +14 -60
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +19 -11
- data/lib/safrano.rb +18 -28
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +43 -0
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/{multipart.rb → safrano/multipart.rb} +37 -41
- data/lib/safrano/rack_app.rb +175 -0
- data/lib/{odata_rack_builder.rb → safrano/rack_builder.rb} +18 -2
- data/lib/{request.rb → safrano/request.rb} +102 -50
- data/lib/{response.rb → safrano/response.rb} +5 -4
- data/lib/safrano/sequel_join_by_paths.rb +5 -0
- data/lib/{service.rb → safrano/service.rb} +257 -188
- data/lib/safrano/version.rb +5 -0
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +53 -17
- data/lib/rack_app.rb +0 -174
- data/lib/sequel_join_by_paths.rb +0 -5
- data/lib/version.rb +0 -4
@@ -1,137 +1,92 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rexml/document'
|
4
|
-
require 'odata/relations
|
5
|
-
require 'odata/batch
|
6
|
-
require 'odata/
|
7
|
-
|
8
|
-
|
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+)
|
13
|
-
|
14
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
102
|
-
{ uri: entity.uri
|
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
|
-
|
106
|
-
|
107
|
-
|
68
|
+
when :expand_c
|
69
|
+
arg.each do |attr, templ|
|
70
|
+
next unless (encoll = entity.send(attr))
|
108
71
|
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
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
|
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 =
|
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
|
-
|
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 =
|
196
|
-
@relman =
|
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 =
|
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 =
|
174
|
+
@v1 = Safrano::ServiceV1.new
|
220
175
|
copy_attribs_to @v1
|
221
176
|
end
|
222
177
|
|
223
178
|
def enable_v2_service
|
224
|
-
@v2 =
|
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
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
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
|
272
|
+
modelklass.include Safrano::MediaEntity
|
290
273
|
else
|
291
|
-
modelklass.extend
|
292
|
-
modelklass.include
|
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
|
-
|
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
|
-
#
|
352
|
-
|
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(
|
405
|
+
def service_xml(_req)
|
375
406
|
doc = REXML::Document.new
|
376
407
|
# separator required ? ?
|
377
|
-
root = doc.add_element('service', 'xml:base' =>
|
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.
|
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, @
|
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' =>
|
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
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
-
|
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
|
-
#
|
525
|
-
#
|
526
|
-
|
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/
|
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:,
|
611
|
+
def get_coll_odata_links_h(array:, icount: nil)
|
544
612
|
array.map do |w|
|
545
|
-
get_entity_odata_link_h(entity: w
|
613
|
+
get_entity_odata_link_h(entity: w)
|
546
614
|
end
|
547
615
|
end
|
548
616
|
|
549
|
-
def get_coll_odata_h(array:,
|
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
|
-
|
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:,
|
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
|
638
|
+
get_entity_odata_link_h(entity: w)
|
571
639
|
end
|
572
640
|
if icount
|
573
|
-
{
|
641
|
+
{ RESULTS_K => res, COUNT_K => icount }
|
574
642
|
else
|
575
|
-
{
|
643
|
+
{ RESULTS_K => res }
|
576
644
|
end
|
577
645
|
end
|
578
646
|
|
579
647
|
def get_emptycoll_odata_h
|
580
|
-
{
|
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
|
-
|
593
|
-
trans: 'transition_end')]
|
662
|
+
ALLOWED_TRANSITIONS_FIXED
|
594
663
|
end
|
595
664
|
|
596
665
|
def transition_end(_match_result)
|
597
|
-
|
666
|
+
Safrano::Transition::RESULT_END
|
598
667
|
end
|
599
668
|
|
600
669
|
def odata_get(req)
|