solis 0.94.0 → 0.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,732 @@
1
+ require 'securerandom'
2
+ require 'iso8601'
3
+ require 'hashdiff'
4
+ require_relative 'query'
5
+
6
+ module Solis
7
+ class Model
8
+
9
+ 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
10
+
11
+ def initialize(attributes = {})
12
+ @model_name = self.class.name
13
+ @model_plural_name = @model_name.pluralize
14
+ @language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || 'en'
15
+
16
+ raise "Please look at /#{@model_name.tableize}/model for structure to supply" if attributes.nil?
17
+
18
+ attributes.each do |attribute, value|
19
+ if self.class.metadata[:attributes].keys.include?(attribute.to_s)
20
+ 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))
21
+ raise Solis::Error::InvalidAttributeError, "'#{@model_name}.#{attribute}' must be an object"
22
+ end
23
+
24
+ if self.class.metadata[:attributes][attribute.to_s][:node_kind].is_a?(RDF::URI) && value.is_a?(Hash)
25
+ inner_model = self.class.graph.shape_as_model(self.class.metadata[:attributes][attribute.to_s][:datatype].to_s)
26
+ value = inner_model.new(value)
27
+ elsif self.class.metadata[:attributes][attribute.to_s][:node_kind].is_a?(RDF::URI) && value.is_a?(Array)
28
+ new_value = []
29
+ value.each do |v|
30
+ if v.is_a?(Hash)
31
+ inner_model = self.class.graph.shape_as_model(self.class.metadata[:attributes][attribute.to_s][:datatype].to_s)
32
+ new_value << inner_model.new(v)
33
+ else
34
+ new_value << v
35
+ end
36
+ end
37
+ value = new_value
38
+ end
39
+
40
+ # switched off. currently language query parameters returns the value
41
+ # value = {
42
+ # "@language" => @language,
43
+ # "@value" => value
44
+ # } if self.class.metadata[:attributes][attribute.to_s][:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
45
+
46
+ value = value.first if value.is_a?(Array) && (attribute.eql?('id') || attribute.eql?(:id))
47
+
48
+ instance_variable_set("@#{attribute}", value)
49
+ else
50
+ raise Solis::Error::InvalidAttributeError, "'#{attribute}' is not part of the definition of #{@model_name}"
51
+ end
52
+ end
53
+
54
+ self.class.make_id_for(self)
55
+ # id = instance_variable_get("@id")
56
+ # if id.nil? || (id.is_a?(String) && id&.empty?)
57
+ # instance_variable_set("@id", SecureRandom.uuid)
58
+ # end
59
+ rescue StandardError => e
60
+ Solis::LOGGER.error(e.message)
61
+ raise Solis::Error::GeneralError, "Unable to create entity #{@model_name}"
62
+ end
63
+
64
+ def name(plural = false)
65
+ if plural
66
+ @model_plural_name
67
+ else
68
+ @model_name
69
+ end
70
+ end
71
+
72
+ def query
73
+ raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
74
+
75
+ # before_read_proc&.call(self)
76
+ result = Solis::Query.new(self)
77
+ # after_read_proc&.call(result)
78
+ result
79
+ end
80
+
81
+ def to_ttl(resolve_all = true)
82
+ graph = as_graph(self, resolve_all)
83
+ graph.dump(:ttl)
84
+ end
85
+
86
+ def dump(format = :ttl, resolve_all = true)
87
+ graph = as_graph(self, resolve_all)
88
+ graph.dump(format)
89
+ end
90
+
91
+ def to_graph(resolve_all = true)
92
+ as_graph(self, resolve_all)
93
+ end
94
+
95
+ def valid?
96
+ begin
97
+ graph = as_graph(self, false)
98
+ rescue Solis::Error::InvalidAttributeError => e
99
+ Solis::LOGGER.error(e.message)
100
+ end
101
+
102
+ shacl = SHACL.get_shapes(self.class.graph.instance_variable_get(:"@graph"))
103
+ report = shacl.execute(graph)
104
+
105
+ report.conform?
106
+ rescue StandardError => e
107
+ false
108
+ end
109
+
110
+ def destroy
111
+ raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
112
+ sparql = Solis::Store::Sparql::Client.new(self.class.sparql_endpoint)
113
+
114
+ raise Solis::Error::QueryError, "#{self.id} is still referenced, refusing to delete" if is_referenced?(sparql)
115
+ # sparql.query('delete{}')
116
+ before_delete_proc&.call(self)
117
+ # graph = as_graph(self, false)
118
+ # Solis::LOGGER.info graph.dump(:ttl) if ConfigFile[:debug]
119
+
120
+ query = %(
121
+ with <#{self.class.graph_name}>
122
+ delete {?s ?p ?o}
123
+ where {
124
+ values ?s {<#{self.graph_id}>}
125
+ ?s ?p ?o }
126
+ )
127
+ result = sparql.query(query)
128
+
129
+ if result.count > 0
130
+ if result.first.bound?(result.variable_names.first) && result.first[result.variable_names.first].value =~ /done$/
131
+ after_delete_proc&.call(self)
132
+ else
133
+ after_delete_proc&.call(result)
134
+ end
135
+ end
136
+ # result = sparql.delete_data(graph, graph: graph.name)
137
+ result
138
+ end
139
+
140
+ def save(validate_dependencies = true, top_level = true)
141
+ raise "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
142
+ sparql = SPARQL::Client.new(self.class.sparql_endpoint)
143
+
144
+ before_create_proc&.call(self)
145
+
146
+ if self.exists?(sparql)
147
+ data = properties_to_hash(self)
148
+
149
+ result = update(data)
150
+ else
151
+ data = properties_to_hash(self)
152
+ attributes = data.include?('attributes') ? data['attributes'] : data
153
+ attributes.each_pair do |key, value| # check each key. if it is an entity process it
154
+ unless self.class.metadata[:attributes][key][:node].nil? #is it an entity
155
+ value = [value] unless value.is_a?(Array)
156
+ value.each do |sub_value|
157
+ embedded = self.class.graph.shape_as_model(self.class.metadata[:attributes][key][:datatype].to_s).new(sub_value)
158
+ embedded_readonly_entities = Solis::Options.instance.get[:embedded_readonly].map{|s| s.to_s} || []
159
+
160
+ if (embedded.class.ancestors.map{|s| s.to_s} & embedded_readonly_entities).empty? || top_level
161
+ if embedded.exists?(sparql)
162
+ embedded_data = properties_to_hash(embedded)
163
+ embedded.update(embedded_data, validate_dependencies, false)
164
+ else
165
+ embedded.save(validate_dependencies, false)
166
+ end
167
+ else
168
+ Solis::LOGGER.info("#{embedded.class.name} is embedded not allowed to change. Skipping")
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ graph = as_graph(self, validate_dependencies)
175
+
176
+ # File.open('/Users/mehmetc/Dropbox/AllSources/LP/graphiti-api/save.ttl', 'wb') do |file|
177
+ # file.puts graph.dump(:ttl)
178
+ # end
179
+ Solis::LOGGER.info SPARQL::Client::Update::InsertData.new(graph, graph: graph.name).to_s if ConfigFile[:debug]
180
+
181
+ result = sparql.insert_data(graph, graph: graph.name)
182
+ end
183
+
184
+ after_create_proc&.call(self)
185
+ self
186
+ rescue StandardError => e
187
+ Solis::LOGGER.error e.message
188
+ Solis::LOGGER.error e.message
189
+ raise e
190
+ end
191
+
192
+ def update(data, validate_dependencies = true, top_level = true)
193
+ raise Solis::Error::GeneralError, "I need a SPARQL endpoint" if self.class.sparql_endpoint.nil?
194
+
195
+ attributes = data.include?('attributes') ? data['attributes'] : data
196
+ raise "id is mandatory when updating" unless attributes.keys.include?('id')
197
+
198
+ id = attributes.delete('id')
199
+
200
+ sparql = SPARQL::Client.new(self.class.sparql_endpoint)
201
+ #sparql = Solis::Store::Sparql::Client.new(self.class.sparql_endpoint, self.class.graph_name)
202
+
203
+ original_klass = self.query.filter({ language: nil, filters: { id: [id] } }).find_all.map { |m| m }&.first
204
+ raise Solis::Error::NotFoundError if original_klass.nil?
205
+ updated_klass = original_klass.deep_dup
206
+
207
+ attributes.each_pair do |key, value| # check each key. if it is an entity process it
208
+ unless original_klass.class.metadata[:attributes][key][:node].nil? #it is an entity
209
+ value = [value] unless value.is_a?(Array)
210
+ value.each do |sub_value|
211
+ embedded = self.class.graph.shape_as_model(original_klass.class.metadata[:attributes][key][:datatype].to_s).new(sub_value)
212
+
213
+ embedded_readonly_entities = Solis::Options.instance.get[:embedded_readonly].map{|s| s.to_s} || []
214
+
215
+ if (embedded.class.ancestors.map{|s| s.to_s} & embedded_readonly_entities).empty? || top_level
216
+ if embedded.exists?(sparql)
217
+ embedded_data = properties_to_hash(embedded)
218
+ embedded.update(embedded_data, validate_dependencies, false)
219
+ else
220
+ embedded_value = embedded.save(validate_dependencies, false)
221
+ value = updated_klass.instance_variable_get("@#{key}").deep_dup
222
+ if value.is_a?(Array)
223
+ value << embedded_value
224
+ else
225
+ value = embedded_value
226
+ end
227
+ end
228
+ else
229
+ Solis::LOGGER.info("#{embedded.class.name} is embedded not allowed to change. Skipping")
230
+ end
231
+ end
232
+ end
233
+
234
+ maxcount = original_klass.class.metadata[:attributes][key][:maxcount]
235
+ value = value.first if maxcount && maxcount == 1 && value.is_a?(Array)
236
+
237
+ updated_klass.instance_variable_set("@#{key}", value)
238
+ end
239
+
240
+ before_update_proc&.call(original_klass, updated_klass)
241
+
242
+ properties_orignal_klass = properties_to_hash(original_klass)
243
+ properties_updated_klass = properties_to_hash(updated_klass)
244
+
245
+ if Hashdiff.best_diff(properties_orignal_klass, properties_updated_klass).empty?
246
+ Solis::LOGGER.info("#{original_klass.class.name} unchanged, skipping")
247
+ data = self.query.filter({ filters: { id: [id] } }).find_all.map { |m| m }&.first
248
+ else
249
+
250
+ delete_graph = as_graph(original_klass, false)
251
+
252
+ where_graph = RDF::Graph.new(graph_name: RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{id}"), data: RDF::Repository.new)
253
+
254
+ if id.is_a?(Array)
255
+ id.each do |i|
256
+ where_graph << [RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{i}"), :p, :o]
257
+ end
258
+ else
259
+ where_graph << [RDF::URI("#{self.class.graph_name}#{self.name.tableize}/#{id}"), :p, :o]
260
+ end
261
+
262
+ insert_graph = as_graph(updated_klass, true)
263
+
264
+ # puts delete_graph.dump(:ttl) if ConfigFile[:debug]
265
+ # puts insert_graph.dump(:ttl) if ConfigFile[:debug]
266
+ # puts where_graph.dump(:ttl) if ConfigFile[:debug]
267
+
268
+ # if ConfigFile[:debug]
269
+ delete_insert_query = SPARQL::Client::Update::DeleteInsert.new(delete_graph, insert_graph, where_graph, graph: insert_graph.name).to_s
270
+ delete_insert_query.gsub!('_:p', '?p')
271
+ # puts delete_insert_query
272
+ data = sparql.query(delete_insert_query)
273
+ # pp data
274
+ # end
275
+
276
+ # sparql.delete_insert(delete_graph, insert_graph, where_graph, graph: insert_graph.name)
277
+
278
+ data = self.query.filter({ filters: { id: [id] } }).find_all.map { |m| m }&.first
279
+ if data.nil?
280
+ sparql.insert_data(insert_graph, graph: insert_graph.name)
281
+ data = self.query.filter({ filters: { id: [id] } }).find_all.map { |m| m }&.first
282
+ end
283
+ end
284
+ after_update_proc&.call(updated_klass, data)
285
+
286
+ data
287
+ #rescue EOFError => e
288
+ rescue StandardError => e
289
+ original_graph = as_graph(original_klass, false)
290
+ Solis::LOGGER.error(e.message)
291
+ Solis::LOGGER.error original_graph.dump(:ttl)
292
+ Solis::LOGGER.error delete_insert_query
293
+ sparql.insert_data(original_graph, graph: original_graph.name)
294
+
295
+ raise e
296
+ end
297
+
298
+ def graph_id
299
+ "#{self.class.graph_name}#{self.name.tableize}/#{self.id}"
300
+ end
301
+
302
+ def is_referenced?(sparql)
303
+ sparql.query("ASK WHERE { ?s ?p <#{self.graph_id}>. filter (!contains(str(?s), 'audit') && !contains(str(?p), 'audit'))}")
304
+ end
305
+
306
+ def exists?(sparql)
307
+ sparql.query("ASK WHERE { <#{self.graph_id}> ?p ?o }")
308
+ end
309
+
310
+ def self.make_id_for(model)
311
+ raise "I need a SPARQL endpoint" if self.sparql_endpoint.nil?
312
+ sparql = Solis::Store::Sparql::Client.new(self.sparql_endpoint)
313
+ id = model.instance_variable_get("@id")
314
+ if id.nil? || (id.is_a?(String) && id&.empty?)
315
+ id_retries = 0
316
+
317
+ while id.nil? || sparql.query("ASK WHERE { ?s <#{self.graph_name}id> \"#{id}\" }")
318
+ id = SecureRandom.uuid
319
+ id_retries+=1
320
+ end
321
+ LOGGER.info("ID(#{id}) generated for #{self.name} in #{id_retries} retries") if ConfigFile[:debug]
322
+ model.instance_variable_set("@id", id)
323
+ elsif id.to_s =~ /^https?:\/\//
324
+ id = id.to_s.split('/').last
325
+ LOGGER.info("ID(#{id}) normalised for #{self.name}") if ConfigFile[:debug]
326
+ model.instance_variable_set("@id", id)
327
+ end
328
+ model
329
+ rescue StandardError => e
330
+ Solis::LOGGER.error(e.message)
331
+ raise Solis::Error::GeneralError, "Error generating id for #{@model_name}"
332
+ end
333
+
334
+ def self.metadata
335
+ @metadata
336
+ end
337
+
338
+ def self.metadata=(m)
339
+ @metadata = m
340
+ end
341
+
342
+ def self.shapes=(s)
343
+ @shapes = s
344
+ end
345
+
346
+ def self.shapes
347
+ @shapes
348
+ end
349
+
350
+ def self.graph_name
351
+ @graph_name
352
+ end
353
+
354
+ def self.graph_name=(graph_name)
355
+ @graph_name = graph_name
356
+ end
357
+
358
+ def self.graph_prefix=(graph_prefix)
359
+ @graph_prefix = graph_prefix
360
+ end
361
+
362
+ def self.graph_prefix
363
+ @graph_prefix
364
+ end
365
+
366
+ def self.sparql_endpoint
367
+ @sparql_endpoint
368
+ end
369
+
370
+ def self.sparql_endpoint=(sparql_endpoint)
371
+ @sparql_endpoint = sparql_endpoint
372
+ end
373
+
374
+ def self.graph
375
+ @graph
376
+ end
377
+
378
+ def self.graph=(graph)
379
+ @graph = graph
380
+ end
381
+
382
+ def self.language
383
+ Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || @language || 'en'
384
+ end
385
+
386
+ def self.language=(language)
387
+ @language = language
388
+ end
389
+
390
+ def self.model(level = 0)
391
+ m = { type: self.name.tableize, attributes: {} }
392
+ self.metadata[:attributes].each do |attribute, attribute_metadata|
393
+
394
+ if attribute_metadata.key?(:class) && !attribute_metadata[:class].nil? && attribute_metadata[:class].value =~ /#{self.graph_name}/ && level == 0
395
+ cm = self.graph.shape_as_model(self.metadata[:attributes][attribute][:datatype].to_s).model(level + 1)
396
+ m[:attributes][attribute.to_sym] = cm[:attributes]
397
+ else
398
+ m[:attributes][attribute.to_sym] = { description: attribute_metadata[:comment]&.value,
399
+ mandatory: (attribute_metadata[:mincount].to_i > 0),
400
+ data_type: attribute_metadata[:datatype] }
401
+ end
402
+ end
403
+
404
+ m
405
+ end
406
+
407
+ def self.model_template(level = 0)
408
+ m = { type: self.name.tableize, attributes: {} }
409
+ self.metadata[:attributes].each do |attribute, attribute_metadata|
410
+
411
+ if attribute_metadata.key?(:class) && !attribute_metadata[:class].nil? && attribute_metadata[:class].value =~ /#{self.graph_name}/ && level == 0
412
+ cm = self.graph.shape_as_model(self.metadata[:attributes][attribute][:datatype].to_s).model_template(level + 1)
413
+ m[:attributes][attribute.to_sym] = cm[:attributes]
414
+ else
415
+ m[:attributes][attribute.to_sym] = ''
416
+ end
417
+ end
418
+
419
+ m
420
+ end
421
+
422
+ def self.construct(level = 0)
423
+ raise 'to bo implemented'
424
+ end
425
+
426
+ def self.model_before_read(&blk)
427
+ self.before_read_proc = blk
428
+ end
429
+
430
+ def self.model_after_read(&blk)
431
+ self.after_read_proc = blk
432
+ end
433
+
434
+ def self.model_before_create(&blk)
435
+ self.before_create_proc = blk
436
+ end
437
+
438
+ def self.model_after_create(&blk)
439
+ self.after_create_proc = blk
440
+ end
441
+
442
+ def self.model_before_update(&blk)
443
+ self.before_update_proc = blk
444
+ end
445
+
446
+ def self.model_after_update(&blk)
447
+ self.after_update_proc = blk
448
+ end
449
+
450
+ def self.model_before_delete(&blk)
451
+ self.before_delete_proc = blk
452
+ end
453
+
454
+ def self.model_after_delete(&blk)
455
+ self.after_delete_proc = blk
456
+ end
457
+
458
+ private
459
+
460
+ def as_graph(klass = self, resolve_all = true)
461
+ graph = RDF::Graph.new
462
+ graph.name = RDF::URI(self.class.graph_name)
463
+ id = build_ttl_objekt2(graph, klass, [], resolve_all)
464
+
465
+ graph
466
+ end
467
+
468
+ def build_ttl_objekt2(graph, klass, hierarchy = [], resolve_all = true)
469
+ hierarchy.push("#{klass.name}(#{klass.instance_variables.include?(:@id) ? klass.instance_variable_get("@id") : ''})")
470
+
471
+ graph_name = self.class.graph_name
472
+ klass_name = klass.class.name
473
+ klass_metadata = klass.class.metadata
474
+ uuid = klass.instance_variable_get("@id") || SecureRandom.uuid
475
+ id = RDF::URI("#{graph_name}#{klass_name.tableize}/#{uuid}")
476
+
477
+ graph << [id, RDF::RDFV.type, klass_metadata[:target_class]]
478
+
479
+ # load existing object and overwrite
480
+ original_klass = klass.query.filter({ filters: { id: [uuid] } }).find_all { |f| f.id == uuid }.first || nil
481
+
482
+ if original_klass.nil?
483
+ original_klass = klass
484
+ else
485
+ resolve_all = false
486
+ klass.instance_variables.map { |m| m.to_s.gsub(/^@/, '') }
487
+ .select { |s| !["model_name", "model_plural_name"]
488
+ .include?(s) }.each do |attribute, value|
489
+ data = klass.instance_variable_get("@#{attribute}")
490
+ original_data = original_klass.instance_variable_get("@#{attribute.to_s}")
491
+ original_klass.instance_variable_set("@#{attribute}", data) unless original_data.eql?(data)
492
+ end
493
+ end
494
+
495
+ begin
496
+ make_graph(graph, hierarchy, id, original_klass, klass_metadata, resolve_all)
497
+ rescue => e
498
+ Solis::LOGGER.error(e.message)
499
+ raise e
500
+ end
501
+
502
+ hierarchy.pop
503
+ id
504
+ end
505
+
506
+ def make_graph(graph, hierarchy, id, klass, klass_metadata, resolve_all)
507
+ klass_metadata[:attributes].each do |attribute, metadata|
508
+ data = klass.instance_variable_get("@#{attribute}")
509
+
510
+ if data.nil? && metadata.key?(:mincount) && ( metadata[:mincount].nil? || metadata[:mincount] > 0) && graph.query(RDF::Query.new({ attribute.to_sym => { RDF.type => metadata[:node] } })).size == 0
511
+ if data.nil?
512
+ uuid = id.value.split('/').last
513
+ original_klass = klass.query.filter({ filters: { id: [ uuid ] } }).find_all { |f| f.id == uuid }.first || nil
514
+ unless original_klass.nil?
515
+ klass = original_klass
516
+ data = klass.instance_variable_get("@#{attribute}")
517
+ end
518
+ end
519
+ #if data is still nil
520
+ raise Solis::Error::InvalidAttributeError, "#{hierarchy.join('.')}~#{klass.name}.#{attribute} min=#{metadata[:mincount]} and max=#{metadata[:maxcount]}" if data.nil?
521
+ end
522
+
523
+ if data && metadata.key?(:maxcount) && ( metadata[:maxcount] && metadata[:maxcount] > 0) && graph.query(SPARQL.parse("select (count(?s) as ?max_subject) where { ?s #{self.class.graph_prefix}:#{attribute} ?p}")).first.max_subject > metadata[:maxcount].to_i
524
+ raise Solis::Error::InvalidAttributeError, "#{hierarchy.join('.')}~#{klass.name}.#{attribute} min=#{metadata[:mincount]} and max=#{metadata[:maxcount]}" if data.nil?
525
+ end
526
+
527
+ # skip if nil or an object that is empty
528
+ next if data.nil? || ([Hash, Array, String].include?(data.class) && data&.empty?)
529
+
530
+ case metadata[:datatype_rdf]
531
+ when 'http://www.w3.org/2001/XMLSchema#boolean'
532
+ data = false if data.nil?
533
+ when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON'
534
+ data = data.to_json
535
+ end
536
+
537
+ # make it an object
538
+ unless metadata[:node_kind].nil?
539
+ model = self.class.graph.shape_as_model(metadata[:datatype].to_s)
540
+ if data.is_a?(Hash)
541
+ data = model.new(data)
542
+ elsif data.is_a?(Array)
543
+ data = data.map { |m| m.is_a?(Hash) ? model.new(m) : m }
544
+ end
545
+ end
546
+
547
+ data = [data] unless data.is_a?(Array)
548
+
549
+ data.each do |d|
550
+ if defined?(d.name) && self.class.graph.shape?(d.name) && resolve_all
551
+ if self.class.graph.shape_as_model(d.name.to_s).metadata[:attributes].select { |_, v| v[:node_kind].is_a?(RDF::URI) }.size > 0 &&
552
+ hierarchy.select { |s| s =~ /^#{d.name.to_s}/ }.size == 0
553
+ internal_resolve = false
554
+ d = build_ttl_objekt2(graph, d, hierarchy, internal_resolve)
555
+ elsif self.class.graph.shape_as_model(d.name.to_s) && hierarchy.select { |s| s =~ /^#{d.name.to_s}/ }.size == 0
556
+ internal_resolve = false
557
+ d = build_ttl_objekt2(graph, d, hierarchy, internal_resolve)
558
+ else
559
+ # d = "#{klass.class.graph_name}#{attribute.tableize}/#{d.id}"
560
+ d = "#{klass.class.graph_name}#{d.name.tableize}/#{d.id}"
561
+ end
562
+ elsif defined?(d.name) && self.class.graph.shape?(d.name)
563
+ d = "#{klass.class.graph_name}#{d.name.tableize}/#{d.id}"
564
+ end
565
+
566
+ if d.is_a?(Array) && d.length == 1
567
+ d = d.first
568
+ end
569
+
570
+ d = if metadata[:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
571
+ if d.is_a?(Hash) && (d.keys - ["@language", "@value"]).size == 0
572
+ if d['@value'].is_a?(Array)
573
+ d_r = []
574
+ d['@value'].each do |v|
575
+ d_r << RDF::Literal.new(v, language: d['@language'])
576
+ end
577
+ d_r
578
+ else
579
+ RDF::Literal.new(d['@value'], language: d['@language'])
580
+ end
581
+ else
582
+ RDF::Literal.new(d, language: @language)
583
+ end
584
+ elsif metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#anyURI') || metadata[:node].is_a?(RDF::URI)
585
+ RDF::URI(d)
586
+ elsif metadata[:datatype_rdf].eql?('http://www.w3.org/2006/time#DateTimeInterval')
587
+ begin
588
+ datatype = metadata[:datatype_rdf]
589
+ RDF::Literal.new(ISO8601::TimeInterval.parse(d).to_s, datatype: datatype)
590
+ rescue StandardError => e
591
+ raise Solis::Error::InvalidDatatypeError, "#{hierarchy.join('.')}.#{attribute}: #{e.message}"
592
+ end
593
+ else
594
+ datatype = RDF::Vocabulary.find_term(metadata[:datatype_rdf])
595
+ datatype = metadata[:node] if datatype.nil?
596
+ datatype = metadata[:datatype_rdf] if datatype.nil?
597
+ RDF::Literal.new(d, datatype: datatype)
598
+ end
599
+
600
+ unless d.valid?
601
+ LOGGER.warn("Invalid datatype for #{hierarchy.join('.')}.#{attribute}")
602
+ end
603
+
604
+ if d.is_a?(Array)
605
+ d.each do |v|
606
+ graph << [id, RDF::URI("#{metadata[:path]}"), v]
607
+ end
608
+ else
609
+ graph << [id, RDF::URI("#{metadata[:path]}"), d]
610
+ end
611
+ end
612
+ end
613
+ rescue StandardError => e
614
+ Solis::LOGGER.error(e.message)
615
+ raise e
616
+ end
617
+
618
+ def build_ttl_objekt(graph, klass, hierarchy = [], resolve_all = true)
619
+ hierarchy.push("#{klass.name}(#{klass.instance_variables.include?(:@id) ? klass.instance_variable_get("@id") : ''})")
620
+ sparql_endpoint = self.class.sparql_endpoint
621
+ if klass.instance_variables.include?(:@id) && hierarchy.length > 1
622
+ unless sparql_endpoint.nil?
623
+ existing_klass = klass.query.filter({ filters: { id: [klass.instance_variable_get("@id")] } }).find_all { |f| f.id == klass.instance_variable_get("@id") }
624
+ if !existing_klass.nil? && !existing_klass.empty? && existing_klass.first.is_a?(klass.class)
625
+ klass = existing_klass.first
626
+ end
627
+ end
628
+ end
629
+
630
+ uuid = klass.instance_variable_get("@id") || SecureRandom.uuid
631
+ id = RDF::URI("#{self.class.graph_name}#{klass.class.name.tableize}/#{uuid}")
632
+ graph << [id, RDF::RDFV.type, klass.class.metadata[:target_class]]
633
+
634
+ klass.class.metadata[:attributes].each do |attribute, metadata|
635
+ data = klass.instance_variable_get("@#{attribute}")
636
+ if data.nil? && metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#boolean')
637
+ data = false
638
+ end
639
+
640
+ if metadata[:datatype_rdf].eql?("http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON")
641
+ data = data.to_json
642
+ end
643
+
644
+ if data.nil? && metadata[:mincount] > 0
645
+ raise Solis::Error::InvalidAttributeError, "#{hierarchy.join('.')}.#{attribute} min=#{metadata[:mincount]} and max=#{metadata[:maxcount]}"
646
+ end
647
+
648
+ next if data.nil? || ([Hash, Array, String].include?(data.class) && data&.empty?)
649
+
650
+ data = [data] unless data.is_a?(Array)
651
+ model = nil
652
+ model = klass.class.graph.shape_as_model(klass.class.metadata[:attributes][attribute][:datatype].to_s) unless klass.class.metadata[:attributes][attribute][:node_kind].nil?
653
+
654
+ data.each do |d|
655
+ original_d = d
656
+ if model
657
+ target_node = model.metadata[:target_node].value.split('/').last.gsub(/Shape$/, '')
658
+ if model.ancestors[0..model.ancestors.find_index(Solis::Model) - 1].map { |m| m.name }.include?(target_node)
659
+ parent_model = model.graph.shape_as_model(target_node)
660
+ end
661
+ end
662
+
663
+ if model && d.is_a?(Hash)
664
+ # TODO: figure out in what use case we need the parent_model
665
+ # model_instance = if parent_model
666
+ # parent_model.new(d)
667
+ # else
668
+ # model.new(d)
669
+ # end
670
+
671
+ # model_instance = model.new(d)
672
+ model_instance = model.descendants.map { |m| m&.new(d) rescue nil }.compact.first || nil
673
+ model_instance = model.new(d) if model_instance.nil?
674
+
675
+ if resolve_all
676
+ d = build_ttl_objekt(graph, model_instance, hierarchy, false)
677
+ else
678
+ real_model = model_instance.query.filter({ filters: { id: model_instance.id } }).find_all { |f| f.id == model_instance.id }&.first
679
+ d = RDF::URI("#{self.class.graph_name}#{real_model ? real_model.name.tableize : model_instance.name.tableize}/#{model_instance.id}")
680
+ end
681
+ elsif model && d.is_a?(model)
682
+ if resolve_all
683
+ if parent_model
684
+ model_instance = parent_model.new({ id: d.id })
685
+ d = build_ttl_objekt(graph, model_instance, hierarchy, false)
686
+ else
687
+ d = build_ttl_objekt(graph, d, hierarchy, false)
688
+ end
689
+ else
690
+ real_model = model.new.query.filter({ filters: { id: d.id } }).find_all { |f| f.id == d.id }&.first
691
+ d = RDF::URI("#{self.class.graph_name}#{real_model ? real_model.name.tableize : model.name.tableize}/#{d.id}")
692
+ end
693
+ else
694
+ datatype = RDF::Vocabulary.find_term(metadata[:datatype_rdf] || metadata[:node])
695
+ if datatype && datatype.datatype?
696
+ d = if metadata[:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
697
+ RDF::Literal.new(d, language: self.class.language)
698
+ else
699
+ if metadata[:datatype_rdf].eql?('http://www.w3.org/2001/XMLSchema#anyURI')
700
+ RDF::URI(d)
701
+ else
702
+ RDF::Literal.new(d, datatype: datatype)
703
+ end
704
+ end
705
+ d = (d.object.value rescue d.object) unless d.valid?
706
+ end
707
+ end
708
+
709
+ graph << [id, RDF::URI("#{self.class.graph_name}#{attribute}"), d]
710
+ end
711
+ end
712
+ hierarchy.pop
713
+ id
714
+ end
715
+
716
+ def properties_to_hash(model)
717
+ n = {}
718
+ model.class.metadata[:attributes].each_key do |m|
719
+ if model.instance_variable_get("@#{m}").is_a?(Array)
720
+ n[m] = model.instance_variable_get("@#{m}").map { |iv| iv.class.ancestors.include?(Solis::Model) ? properties_to_hash(iv) : iv }
721
+ elsif model.instance_variable_get("@#{m}").class.ancestors.include?(Solis::Model)
722
+ n[m] = properties_to_hash(model.instance_variable_get("@#{m}"))
723
+ else
724
+ n[m] = model.instance_variable_get("@#{m}")
725
+ end
726
+ end
727
+
728
+ n.compact!
729
+ n
730
+ end
731
+ end
732
+ end