solis 0.64.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +287 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/after_hooks.rb +24 -0
- data/examples/config.yml.template +15 -0
- data/examples/read_from_shacl.rb +22 -0
- data/examples/read_from_shacl_abv.rb +84 -0
- data/examples/read_from_sheet.rb +22 -0
- data/lib/solis/config_file.rb +91 -0
- data/lib/solis/error/cursor_error.rb +6 -0
- data/lib/solis/error/general_error.rb +6 -0
- data/lib/solis/error/invalid_attribute_error.rb +6 -0
- data/lib/solis/error/invalid_datatype_error.rb +6 -0
- data/lib/solis/error/not_found_error.rb +6 -0
- data/lib/solis/error/query_error.rb +6 -0
- data/lib/solis/error.rb +3 -0
- data/lib/solis/graph.rb +360 -0
- data/lib/solis/model.rb +565 -0
- data/lib/solis/options.rb +19 -0
- data/lib/solis/query/construct.rb +93 -0
- data/lib/solis/query/filter.rb +133 -0
- data/lib/solis/query/run.rb +97 -0
- data/lib/solis/query.rb +347 -0
- data/lib/solis/resource.rb +37 -0
- data/lib/solis/shape/data_types.rb +280 -0
- data/lib/solis/shape/reader/csv.rb +12 -0
- data/lib/solis/shape/reader/file.rb +16 -0
- data/lib/solis/shape/reader/sheet.rb +777 -0
- data/lib/solis/shape/reader/simple_sheets/sheet.rb +59 -0
- data/lib/solis/shape/reader/simple_sheets/worksheet.rb +173 -0
- data/lib/solis/shape/reader/simple_sheets.rb +40 -0
- data/lib/solis/shape.rb +189 -0
- data/lib/solis/sparql_adaptor.rb +318 -0
- data/lib/solis/store/sparql/client/query.rb +35 -0
- data/lib/solis/store/sparql/client.rb +41 -0
- data/lib/solis/version.rb +3 -0
- data/lib/solis.rb +13 -0
- data/solis.gemspec +50 -0
- metadata +304 -0
data/lib/solis/model.rb
ADDED
@@ -0,0 +1,565 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'iso8601'
|
3
|
+
require_relative 'query'
|
4
|
+
|
5
|
+
module Solis
|
6
|
+
class Model
|
7
|
+
|
8
|
+
class_attribute :before_read_proc, :after_read_proc, :before_create_proc, :after_create_proc, :before_update_proc, :after_update_proc, :before_delete_proc, :after_delete_proc
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
@model_name = self.class.name
|
12
|
+
@model_plural_name = @model_name.pluralize
|
13
|
+
@language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || 'en'
|
14
|
+
|
15
|
+
raise "Please look at /#{@model_name.tableize}/model for structure to supply" if attributes.nil?
|
16
|
+
|
17
|
+
attributes.each do |attribute, value|
|
18
|
+
if self.class.metadata[:attributes].keys.include?(attribute.to_s)
|
19
|
+
if !self.class.metadata[:attributes][attribute.to_s][:node_kind].nil? && !(value.is_a?(Hash) || value.is_a?(Array) || value.class.ancestors.include?(Solis::Model))
|
20
|
+
raise Solis::Error::InvalidAttributeError, "'#{@model_name}.#{attribute}' must be an object"
|
21
|
+
end
|
22
|
+
|
23
|
+
if self.class.metadata[:attributes][attribute.to_s][:node_kind].is_a?(RDF::URI) && value.is_a?(Hash)
|
24
|
+
inner_model = self.class.graph.shape_as_model(self.class.metadata[:attributes][attribute.to_s][:datatype].to_s)
|
25
|
+
value = inner_model.new(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
# switched off. currently language query parameters returns the value
|
29
|
+
# value = {
|
30
|
+
# "@language" => @language,
|
31
|
+
# "@value" => value
|
32
|
+
# } if self.class.metadata[:attributes][attribute.to_s][:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
|
33
|
+
|
34
|
+
value = value.first if value.is_a?(Array) && (attribute.eql?('id') || attribute.eql?(:id))
|
35
|
+
|
36
|
+
instance_variable_set("@#{attribute}", value)
|
37
|
+
else
|
38
|
+
raise Solis::Error::InvalidAttributeError, "'#{attribute}' is not part of the definition of #{@model_name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
self.class.make_id_for(self)
|
43
|
+
# id = instance_variable_get("@id")
|
44
|
+
# if id.nil? || (id.is_a?(String) && id&.empty?)
|
45
|
+
# instance_variable_set("@id", SecureRandom.uuid)
|
46
|
+
# end
|
47
|
+
rescue StandardError => e
|
48
|
+
Solis::LOGGER.error(e.message)
|
49
|
+
raise Solis::Error::GeneralError, "Unable to create entity #{@model_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def name(plural = false)
|
53
|
+
if plural
|
54
|
+
@model_plural_name
|
55
|
+
else
|
56
|
+
@model_name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def query
|
61
|
+
raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
|
62
|
+
|
63
|
+
#before_read_proc&.call(self)
|
64
|
+
result = Solis::Query.new(self)
|
65
|
+
#after_read_proc&.call(result)
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_ttl(resolve_all = true)
|
70
|
+
graph = as_graph(self, resolve_all)
|
71
|
+
graph.dump(:ttl)
|
72
|
+
end
|
73
|
+
|
74
|
+
def dump(format = :ttl, resolve_all = true)
|
75
|
+
graph = as_graph(self, resolve_all)
|
76
|
+
graph.dump(format)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_graph(resolve_all = true)
|
80
|
+
as_graph(self, resolve_all)
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid?
|
84
|
+
begin
|
85
|
+
graph = as_graph(self, false)
|
86
|
+
rescue Solis::Error::InvalidAttributeError => e
|
87
|
+
Solis::LOGGER.error(e.message)
|
88
|
+
end
|
89
|
+
|
90
|
+
shacl = SHACL.get_shapes(self.class.graph.instance_variable_get(:"@graph"))
|
91
|
+
report = shacl.execute(graph)
|
92
|
+
|
93
|
+
report.conform?
|
94
|
+
rescue StandardError => e
|
95
|
+
false
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy
|
99
|
+
raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
|
100
|
+
before_delete_proc&.call(self)
|
101
|
+
|
102
|
+
sparql = SPARQL::Client.new(self.class.sparql_endpoint)
|
103
|
+
graph = as_graph(klass = self, resolve_all = false)
|
104
|
+
Solis::LOGGER.info graph.dump(:ttl) if ConfigFile[:debug]
|
105
|
+
|
106
|
+
result = sparql.delete_data(graph, graph: graph.name)
|
107
|
+
after_delete_proc&.call(result)
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
def save(validate_dependencies = true)
|
112
|
+
raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
|
113
|
+
|
114
|
+
before_create_proc&.call(self)
|
115
|
+
sparql = SPARQL::Client.new(self.class.sparql_endpoint)
|
116
|
+
graph = as_graph(self, validate_dependencies)
|
117
|
+
|
118
|
+
# File.open('/Users/mehmetc/Dropbox/AllSources/LP/graphiti-api/save.ttl', 'wb') do |file|
|
119
|
+
# file.puts graph.dump(:ttl)
|
120
|
+
# end
|
121
|
+
Solis::LOGGER.info SPARQL::Client::Update::InsertData.new(graph, graph: graph.name).to_s if ConfigFile[:debug]
|
122
|
+
|
123
|
+
result = sparql.insert_data(graph, graph: graph.name)
|
124
|
+
after_create_proc&.call(result)
|
125
|
+
result
|
126
|
+
rescue StandardError => e
|
127
|
+
Solis::LOGGER.error e.message
|
128
|
+
Solis::LOGGER.error e.message
|
129
|
+
raise e
|
130
|
+
end
|
131
|
+
|
132
|
+
def update(data, validate_dependencies = true)
|
133
|
+
raise Solis::Error::GeneralError, "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
|
134
|
+
|
135
|
+
attributes = data.include?('attributes') ? data['attributes'] : data
|
136
|
+
raise "id is mandatory in attributes" unless attributes.keys.include?('id')
|
137
|
+
|
138
|
+
id = attributes.delete('id')
|
139
|
+
|
140
|
+
sparql = SPARQL::Client.new(self.class.sparql_endpoint)
|
141
|
+
|
142
|
+
original_klass = self.query.filter({language: nil, filters: { id: [id] } }).find_all.map { |m| m }&.first
|
143
|
+
raise Solis::Error::NotFoundError if original_klass.nil?
|
144
|
+
updated_klass = original_klass.deep_dup
|
145
|
+
|
146
|
+
attributes.each_pair do |key, value|
|
147
|
+
updated_klass.send(:"#{key}=", value)
|
148
|
+
end
|
149
|
+
|
150
|
+
before_update_proc&.call(original_klass, updated_klass)
|
151
|
+
|
152
|
+
delete_graph = as_graph(original_klass, false)
|
153
|
+
# where_graph = RDF::Graph.new
|
154
|
+
# where_graph.name = RDF::URI(self.class.graph_name)
|
155
|
+
|
156
|
+
where_graph = RDF::Graph.new(graph_name: RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{id}"), data: RDF::Repository.new)
|
157
|
+
|
158
|
+
if id.is_a?(Array)
|
159
|
+
id.each do |i|
|
160
|
+
where_graph << [RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{i}"), :p, :o]
|
161
|
+
end
|
162
|
+
else
|
163
|
+
where_graph << [RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{id}"), :p, :o]
|
164
|
+
end
|
165
|
+
|
166
|
+
insert_graph = as_graph(updated_klass, true)
|
167
|
+
|
168
|
+
#Solis::LOGGER.info delete_graph.dump(:ttl) if ConfigFile[:debug]
|
169
|
+
#Solis::LOGGER.info insert_graph.dump(:ttl) if ConfigFile[:debug]
|
170
|
+
#Solis::LOGGER.info where_graph.dump(:ttl) if ConfigFile[:debug]
|
171
|
+
|
172
|
+
#if ConfigFile[:debug]
|
173
|
+
delete_insert_query = SPARQL::Client::Update::DeleteInsert.new(delete_graph, insert_graph, where_graph, graph: insert_graph.name).to_s
|
174
|
+
delete_insert_query.gsub!('_:p', '?p')
|
175
|
+
Solis::LOGGER.info delete_insert_query
|
176
|
+
data = sparql.query(delete_insert_query)
|
177
|
+
pp data
|
178
|
+
#end
|
179
|
+
|
180
|
+
# sparql.delete_insert(delete_graph, insert_graph, where_graph, graph: insert_graph.name)
|
181
|
+
|
182
|
+
data = self.query.filter({ filters: { id: [id] } }).find_all.map { |m| m }&.first
|
183
|
+
if data.nil?
|
184
|
+
sparql.insert_data(insert_graph, graph: insert_graph.name)
|
185
|
+
data = self.query.filter({ filters: { id: [id] } }).find_all.map { |m| m }&.first
|
186
|
+
end
|
187
|
+
|
188
|
+
after_update_proc&.call(updated_klass, data)
|
189
|
+
data
|
190
|
+
rescue StandardError => e
|
191
|
+
original_graph = as_graph(original_klass, false)
|
192
|
+
Solis::LOGGER.error(e.message)
|
193
|
+
Solis::LOGGER.error original_graph.dump(:ttl)
|
194
|
+
sparql.insert_data(original_graph, graph: original_graph.name)
|
195
|
+
|
196
|
+
raise e
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.make_id_for(model)
|
200
|
+
id = model.instance_variable_get("@id")
|
201
|
+
if id.nil? || (id.is_a?(String) && id&.empty?)
|
202
|
+
model.instance_variable_set("@id", SecureRandom.uuid)
|
203
|
+
end
|
204
|
+
model
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.metadata
|
208
|
+
@metadata
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.metadata=(m)
|
212
|
+
@metadata = m
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.shapes=(s)
|
216
|
+
@shapes = s
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.shapes
|
220
|
+
@shapes
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.graph_name
|
224
|
+
@graph_name
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.graph_name=(graph_name)
|
228
|
+
@graph_name = graph_name
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.graph_prefix=(graph_prefix)
|
232
|
+
@graph_prefix = graph_prefix
|
233
|
+
end
|
234
|
+
|
235
|
+
def self.graph_prefix
|
236
|
+
@graph_prefix
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.sparql_endpoint
|
240
|
+
@sparql_endpoint
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.sparql_endpoint=(sparql_endpoint)
|
244
|
+
@sparql_endpoint = sparql_endpoint
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.graph
|
248
|
+
@graph
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.graph=(graph)
|
252
|
+
@graph = graph
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.language
|
256
|
+
Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || @language || 'en'
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.language=(language)
|
260
|
+
@language = language
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.model(level = 0)
|
264
|
+
m = { type: self.name.tableize, attributes: {} }
|
265
|
+
self.metadata[:attributes].each do |attribute, attribute_metadata|
|
266
|
+
|
267
|
+
if attribute_metadata.key?(:class) && !attribute_metadata[:class].nil? && attribute_metadata[:class].value =~ /#{self.graph_name}/ && level == 0
|
268
|
+
cm = self.graph.shape_as_model(self.metadata[:attributes][attribute][:datatype].to_s).model(level + 1)
|
269
|
+
m[:attributes][attribute.to_sym] = cm[:attributes]
|
270
|
+
else
|
271
|
+
m[:attributes][attribute.to_sym] = { description: attribute_metadata[:comment]&.value,
|
272
|
+
mandatory: (attribute_metadata[:mincount].to_i > 0),
|
273
|
+
data_type: attribute_metadata[:datatype] }
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
m
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.model_template(level = 0)
|
281
|
+
m = { type: self.name.tableize, attributes: {} }
|
282
|
+
self.metadata[:attributes].each do |attribute, attribute_metadata|
|
283
|
+
|
284
|
+
if attribute_metadata.key?(:class) && !attribute_metadata[:class].nil? && attribute_metadata[:class].value =~ /#{self.graph_name}/ && level == 0
|
285
|
+
cm = self.graph.shape_as_model(self.metadata[:attributes][attribute][:datatype].to_s).model_template(level + 1)
|
286
|
+
m[:attributes][attribute.to_sym] = cm[:attributes]
|
287
|
+
else
|
288
|
+
m[:attributes][attribute.to_sym] = ''
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
m
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.model_before_read(&blk)
|
296
|
+
self.before_read_proc = blk
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.model_after_read(&blk)
|
300
|
+
self.after_read_proc = blk
|
301
|
+
end
|
302
|
+
|
303
|
+
def self.model_before_create(&blk)
|
304
|
+
self.before_create_proc = blk
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.model_after_create(&blk)
|
308
|
+
self.after_create_proc = blk
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.model_before_update(&blk)
|
312
|
+
self.before_update_proc = blk
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.model_after_update(&blk)
|
316
|
+
self.after_update_proc = blk
|
317
|
+
end
|
318
|
+
|
319
|
+
def self.model_before_delete(&blk)
|
320
|
+
self.before_delete_proc = blk
|
321
|
+
end
|
322
|
+
|
323
|
+
def self.model_after_delete(&blk)
|
324
|
+
self.after_delete_proc = blk
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
|
329
|
+
def as_graph(klass = self, resolve_all = true)
|
330
|
+
graph = RDF::Graph.new
|
331
|
+
graph.name = RDF::URI(self.class.graph_name)
|
332
|
+
id = build_ttl_objekt2(graph, klass, [], resolve_all)
|
333
|
+
|
334
|
+
graph
|
335
|
+
end
|
336
|
+
|
337
|
+
def build_ttl_objekt2(graph, klass, hierarchy = [], resolve_all = true)
|
338
|
+
hierarchy.push("#{klass.name}(#{klass.instance_variables.include?(:@id) ? klass.instance_variable_get("@id") : ''})")
|
339
|
+
|
340
|
+
graph_name = self.class.graph_name
|
341
|
+
klass_name = klass.class.name
|
342
|
+
klass_metadata = klass.class.metadata
|
343
|
+
uuid = klass.instance_variable_get("@id") || SecureRandom.uuid
|
344
|
+
id = RDF::URI("#{graph_name}#{klass_name.tableize}/#{uuid}")
|
345
|
+
|
346
|
+
graph << [id, RDF::RDFV.type, klass_metadata[:target_class]]
|
347
|
+
|
348
|
+
#load existing object and overwrite
|
349
|
+
original_klass = klass.query.filter({ filters: { id: [uuid] } }).find_all { |f| f.id == uuid }.first || nil
|
350
|
+
|
351
|
+
if original_klass.nil?
|
352
|
+
original_klass = klass
|
353
|
+
else
|
354
|
+
resolve_all = false
|
355
|
+
klass.instance_variables.map { |m| m.to_s.gsub(/^@/, '') }
|
356
|
+
.select { |s| !["model_name", "model_plural_name"]
|
357
|
+
.include?(s) }.each do |attribute, value|
|
358
|
+
data = klass.instance_variable_get("@#{attribute}")
|
359
|
+
original_data = original_klass.instance_variable_get("@#{attribute.to_s}")
|
360
|
+
original_klass.instance_variable_set("@#{attribute}", data) unless original_data.eql?(data)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
make_graph(graph, hierarchy, id, original_klass, klass_metadata, resolve_all)
|
365
|
+
|
366
|
+
hierarchy.pop
|
367
|
+
id
|
368
|
+
end
|
369
|
+
|
370
|
+
def make_graph(graph, hierarchy, id, klass, klass_metadata, resolve_all)
|
371
|
+
klass_metadata[:attributes].each do |attribute, metadata|
|
372
|
+
data = klass.instance_variable_get("@#{attribute}")
|
373
|
+
|
374
|
+
raise Solis::Error::InvalidAttributeError,
|
375
|
+
"#{hierarchy.join('.')}.#{attribute} min=#{metadata[:mincount]} and max=#{metadata[:maxcount]}" if data.nil? &&
|
376
|
+
metadata[:mincount] > 0 &&
|
377
|
+
graph.query(RDF::Query.new({ attribute.to_sym => { RDF.type => metadata[:node] } })).size == 0
|
378
|
+
|
379
|
+
# skip if nil or an object that is empty
|
380
|
+
next if data.nil? || ([Hash, Array, String].include?(data.class) && data&.empty?)
|
381
|
+
|
382
|
+
case metadata[:datatype_rdf]
|
383
|
+
when 'http://www.w3.org/2001/XMLSchema#boolean'
|
384
|
+
data = false if data.nil?
|
385
|
+
when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON'
|
386
|
+
data = data.to_json
|
387
|
+
end
|
388
|
+
|
389
|
+
#make it an object
|
390
|
+
unless metadata[:node_kind].nil?
|
391
|
+
model = self.class.graph.shape_as_model(metadata[:datatype].to_s)
|
392
|
+
if data.is_a?(Hash)
|
393
|
+
data = model.new(data)
|
394
|
+
elsif data.is_a?(Array)
|
395
|
+
data = data.map { |m| m.is_a?(Hash) ? model.new(m) : m }
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
data = [data] unless data.is_a?(Array)
|
400
|
+
|
401
|
+
data.each do |d|
|
402
|
+
if defined?(d.name) && self.class.graph.shape?(d.name)
|
403
|
+
if self.class.graph.shape_as_model(d.name.to_s).metadata[:attributes].select{|_,v| v[:node_kind].is_a?(RDF::URI)}.size > 0 &&
|
404
|
+
hierarchy.select{|s| s =~ /^#{d.name.to_s}/}.size == 0
|
405
|
+
internal_resolve = false
|
406
|
+
d = build_ttl_objekt2(graph, d, hierarchy, internal_resolve)
|
407
|
+
elsif self.class.graph.shape_as_model(d.name.to_s) && hierarchy.select{|s| s =~ /^#{d.name.to_s}/}.size == 0
|
408
|
+
internal_resolve = false
|
409
|
+
d = build_ttl_objekt2(graph, d, hierarchy, internal_resolve)
|
410
|
+
else
|
411
|
+
d = "#{klass.class.graph_name}#{attribute.tableize}/#{d.id}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
if d.is_a?(Array) && d.length == 1
|
416
|
+
d = d.first
|
417
|
+
end
|
418
|
+
|
419
|
+
d = if metadata[:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
|
420
|
+
if d.is_a?(Hash) && (d.keys - ["@language", "@value"]).size == 0
|
421
|
+
if d['@value'].is_a?(Array)
|
422
|
+
d_r = []
|
423
|
+
d['@value'].each do |v|
|
424
|
+
d_r << RDF::Literal.new(v, language: d['@language'])
|
425
|
+
end
|
426
|
+
d_r
|
427
|
+
else
|
428
|
+
RDF::Literal.new(d['@value'], language: d['@language'])
|
429
|
+
end
|
430
|
+
else
|
431
|
+
RDF::Literal.new(d, language: @language)
|
432
|
+
end
|
433
|
+
elsif metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#anyURI') || metadata[:node].is_a?(RDF::URI)
|
434
|
+
RDF::URI(d)
|
435
|
+
elsif metadata[:datatype_rdf].eql?('http://www.w3.org/2006/time#DateTimeInterval')
|
436
|
+
begin
|
437
|
+
datatype = metadata[:datatype_rdf]
|
438
|
+
RDF::Literal.new(ISO8601::TimeInterval.parse(d).to_s, datatype: datatype)
|
439
|
+
rescue StandardError => e
|
440
|
+
raise Solis::Error::InvalidDatatypeError, "#{hierarchy.join('.')}.#{attribute}: #{e.message}"
|
441
|
+
end
|
442
|
+
else
|
443
|
+
datatype = RDF::Vocabulary.find_term(metadata[:datatype_rdf])
|
444
|
+
datatype = metadata[:node] if datatype.nil?
|
445
|
+
datatype = metadata[:datatype_rdf] if datatype.nil?
|
446
|
+
RDF::Literal.new(d, datatype: datatype)
|
447
|
+
end
|
448
|
+
|
449
|
+
unless d.valid?
|
450
|
+
LOGGER.warn("Invalid datatype for #{hierarchy.join('.')}.#{attribute}")
|
451
|
+
end
|
452
|
+
|
453
|
+
if d.is_a?(Array)
|
454
|
+
d.each do |v|
|
455
|
+
graph << [id, RDF::URI("#{metadata[:path]}"), v]
|
456
|
+
end
|
457
|
+
else
|
458
|
+
graph << [id, RDF::URI("#{metadata[:path]}"), d]
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
rescue StandardError => e
|
463
|
+
Solis::LOGGER.error(e.message)
|
464
|
+
raise e
|
465
|
+
end
|
466
|
+
|
467
|
+
def build_ttl_objekt(graph, klass, hierarchy = [], resolve_all = true)
|
468
|
+
hierarchy.push("#{klass.name}(#{klass.instance_variables.include?(:@id) ? klass.instance_variable_get("@id") : ''})")
|
469
|
+
sparql_endpoint = self.class.sparql_endpoint
|
470
|
+
if klass.instance_variables.include?(:@id) && hierarchy.length > 1
|
471
|
+
unless sparql_endpoint.nil?
|
472
|
+
existing_klass = klass.query.filter({ filters: { id: [klass.instance_variable_get("@id")] } }).find_all { |f| f.id == klass.instance_variable_get("@id") }
|
473
|
+
if !existing_klass.nil? && !existing_klass.empty? && existing_klass.first.is_a?(klass.class)
|
474
|
+
klass = existing_klass.first
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
uuid = klass.instance_variable_get("@id") || SecureRandom.uuid
|
480
|
+
id = RDF::URI("#{self.class.graph_name}#{klass.class.name.tableize}/#{uuid}")
|
481
|
+
graph << [id, RDF::RDFV.type, klass.class.metadata[:target_class]]
|
482
|
+
|
483
|
+
klass.class.metadata[:attributes].each do |attribute, metadata|
|
484
|
+
data = klass.instance_variable_get("@#{attribute}")
|
485
|
+
if data.nil? && metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#boolean')
|
486
|
+
data = false
|
487
|
+
end
|
488
|
+
|
489
|
+
if metadata[:datatype_rdf].eql?("http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON")
|
490
|
+
data = data.to_json
|
491
|
+
end
|
492
|
+
|
493
|
+
if data.nil? && metadata[:mincount] > 0
|
494
|
+
raise Solis::Error::InvalidAttributeError, "#{hierarchy.join('.')}.#{attribute} min=#{metadata[:mincount]} and max=#{metadata[:maxcount]}"
|
495
|
+
end
|
496
|
+
|
497
|
+
next if data.nil? || ([Hash, Array, String].include?(data.class) && data&.empty?)
|
498
|
+
|
499
|
+
data = [data] unless data.is_a?(Array)
|
500
|
+
model = nil
|
501
|
+
model = klass.class.graph.shape_as_model(klass.class.metadata[:attributes][attribute][:datatype].to_s) unless klass.class.metadata[:attributes][attribute][:node_kind].nil?
|
502
|
+
|
503
|
+
data.each do |d|
|
504
|
+
original_d = d
|
505
|
+
if model
|
506
|
+
target_node = model.metadata[:target_node].value.split('/').last.gsub(/Shape$/, '')
|
507
|
+
if model.ancestors[0..model.ancestors.find_index(Solis::Model) - 1].map { |m| m.name }.include?(target_node)
|
508
|
+
parent_model = model.graph.shape_as_model(target_node)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
if model && d.is_a?(Hash)
|
513
|
+
#TODO: figure out in what use case we need the parent_model
|
514
|
+
# model_instance = if parent_model
|
515
|
+
# parent_model.new(d)
|
516
|
+
# else
|
517
|
+
# model.new(d)
|
518
|
+
# end
|
519
|
+
|
520
|
+
# model_instance = model.new(d)
|
521
|
+
model_instance = model.descendants.map { |m| m&.new(d) rescue nil }.compact.first || nil
|
522
|
+
model_instance = model.new(d) if model_instance.nil?
|
523
|
+
|
524
|
+
if resolve_all
|
525
|
+
d = build_ttl_objekt(graph, model_instance, hierarchy, false)
|
526
|
+
else
|
527
|
+
real_model = model_instance.query.filter({ filters: { id: model_instance.id } }).find_all { |f| f.id == model_instance.id }&.first
|
528
|
+
d = RDF::URI("#{self.class.graph_name}#{real_model ? real_model.name.tableize : model_instance.name.tableize}/#{model_instance.id}")
|
529
|
+
end
|
530
|
+
elsif model && d.is_a?(model)
|
531
|
+
if resolve_all
|
532
|
+
if parent_model
|
533
|
+
model_instance = parent_model.new({ id: d.id })
|
534
|
+
d = build_ttl_objekt(graph, model_instance, hierarchy, false)
|
535
|
+
else
|
536
|
+
d = build_ttl_objekt(graph, d, hierarchy, false)
|
537
|
+
end
|
538
|
+
else
|
539
|
+
real_model = model.new.query.filter({ filters: { id: d.id } }).find_all { |f| f.id == d.id }&.first
|
540
|
+
d = RDF::URI("#{self.class.graph_name}#{real_model ? real_model.name.tableize : model.name.tableize}/#{d.id}")
|
541
|
+
end
|
542
|
+
else
|
543
|
+
datatype = RDF::Vocabulary.find_term(metadata[:datatype_rdf] || metadata[:node])
|
544
|
+
if datatype && datatype.datatype?
|
545
|
+
d = if metadata[:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
|
546
|
+
RDF::Literal.new(d, language: self.class.language)
|
547
|
+
else
|
548
|
+
if metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#anyURI')
|
549
|
+
RDF::URI(d)
|
550
|
+
else
|
551
|
+
RDF::Literal.new(d, datatype: datatype)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
d = (d.object.value rescue d.object) unless d.valid?
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
graph << [id, RDF::URI("#{self.class.graph_name}#{attribute}"), d]
|
559
|
+
end
|
560
|
+
end
|
561
|
+
hierarchy.pop
|
562
|
+
id
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'uuidtools'
|
2
|
+
|
3
|
+
module Solis
|
4
|
+
class Query
|
5
|
+
class Construct
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
@sparql_endpoint = @model.class.sparql_endpoint
|
9
|
+
@sparql_client = SPARQL::Client.new(@sparql_endpoint, graph: @model.class.graph_name, read_timeout: 120)
|
10
|
+
@construct_cache = File.absolute_path(Solis::Options.instance.get[:cache])
|
11
|
+
@moneta = Moneta.new(:File, dir: "#{@construct_cache}/construct", expires: Solis::Options.instance.get[:cache_expire])
|
12
|
+
end
|
13
|
+
|
14
|
+
def exists?
|
15
|
+
File.exist?(file_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def load
|
19
|
+
construct_path = file_path
|
20
|
+
raise Solis::Error::NotFoundError, "Construct not found at #{construct_path} " unless exists?
|
21
|
+
File.read(construct_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def file_path
|
25
|
+
"#{ConfigFile.path}/constructs/#{@model.name.tableize.singularize}.sparql"
|
26
|
+
end
|
27
|
+
|
28
|
+
def file_path_hash
|
29
|
+
UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, file_path).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
construct_query = load
|
34
|
+
sparql_repository = @sparql_endpoint
|
35
|
+
|
36
|
+
from_cache = Graphiti.context[:object].from_cache || '1'
|
37
|
+
if construct_query && construct_query =~ /construct/
|
38
|
+
if @moneta.key?(file_path_hash) && from_cache.eql?('1')
|
39
|
+
sparql_repository = @moneta[file_path_hash]
|
40
|
+
else
|
41
|
+
@sparql_client = SPARQL::Client.new(@sparql_endpoint, read_timeout: 120)
|
42
|
+
result = @sparql_client.query(construct_query)
|
43
|
+
repository=RDF::Repository.new
|
44
|
+
result.each {|s| repository << [s[:s], s[:p], s[:o]]}
|
45
|
+
sparql_repository = repository
|
46
|
+
@moneta.store(file_path_hash, repository, expires: ConfigFile[:solis][:cache_expire] || 86400)
|
47
|
+
end
|
48
|
+
elsif construct_query && construct_query =~ /insert/
|
49
|
+
unless @moneta.key?(file_path_hash)
|
50
|
+
clear_construct
|
51
|
+
result = @sparql_client.query(construct_query)
|
52
|
+
LOGGER.info(result[0]['callret-0'].value) unless result.empty?
|
53
|
+
@moneta.store(file_path_hash, repository, expires: ConfigFile[:solis][:cache_expire] || 86400) unless result[0]['callret-0'].value =~ /0 triples/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#SPARQL::Client.new(@sparql_endpoint, graph: @model.class.graph_name, read_timeout: 120)
|
58
|
+
SPARQL::Client.new(sparql_repository, read_timeout: 120)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def parsed_graph_name
|
64
|
+
URI.parse(@model.class.graph_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def construct_graph_name
|
68
|
+
"#{parsed_graph_name.scheme}://#{@model.name.underscore}.#{parsed_graph_name.host}/"
|
69
|
+
end
|
70
|
+
|
71
|
+
def created_at
|
72
|
+
created_at = nil
|
73
|
+
result = @sparql_client.query("select * from <#{construct_graph_name}> where {<#{construct_graph_name}_metadata> <#{construct_graph_name}created_at> ?_created_at}")
|
74
|
+
unless result.empty?
|
75
|
+
created_at = result[0]._created_at.object
|
76
|
+
end
|
77
|
+
|
78
|
+
created_at
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear_construct
|
82
|
+
result = @sparql_client.query("clear graph <#{construct_graph_name}>")
|
83
|
+
LOGGER.info(result[0]['callret-0'].value)
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_metadata
|
87
|
+
result = @sparql_client.query("insert into <#{construct_graph_name}> { <#{construct_graph_name}_metadata> <#{construct_graph_name}created_at> \"#{Time.now.xmlschema}\"^^xsd:dateTime}")
|
88
|
+
LOGGER.info(result[0]['callret-0'].value)
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|