solis 0.64.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +6 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +287 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/examples/after_hooks.rb +24 -0
  12. data/examples/config.yml.template +15 -0
  13. data/examples/read_from_shacl.rb +22 -0
  14. data/examples/read_from_shacl_abv.rb +84 -0
  15. data/examples/read_from_sheet.rb +22 -0
  16. data/lib/solis/config_file.rb +91 -0
  17. data/lib/solis/error/cursor_error.rb +6 -0
  18. data/lib/solis/error/general_error.rb +6 -0
  19. data/lib/solis/error/invalid_attribute_error.rb +6 -0
  20. data/lib/solis/error/invalid_datatype_error.rb +6 -0
  21. data/lib/solis/error/not_found_error.rb +6 -0
  22. data/lib/solis/error/query_error.rb +6 -0
  23. data/lib/solis/error.rb +3 -0
  24. data/lib/solis/graph.rb +360 -0
  25. data/lib/solis/model.rb +565 -0
  26. data/lib/solis/options.rb +19 -0
  27. data/lib/solis/query/construct.rb +93 -0
  28. data/lib/solis/query/filter.rb +133 -0
  29. data/lib/solis/query/run.rb +97 -0
  30. data/lib/solis/query.rb +347 -0
  31. data/lib/solis/resource.rb +37 -0
  32. data/lib/solis/shape/data_types.rb +280 -0
  33. data/lib/solis/shape/reader/csv.rb +12 -0
  34. data/lib/solis/shape/reader/file.rb +16 -0
  35. data/lib/solis/shape/reader/sheet.rb +777 -0
  36. data/lib/solis/shape/reader/simple_sheets/sheet.rb +59 -0
  37. data/lib/solis/shape/reader/simple_sheets/worksheet.rb +173 -0
  38. data/lib/solis/shape/reader/simple_sheets.rb +40 -0
  39. data/lib/solis/shape.rb +189 -0
  40. data/lib/solis/sparql_adaptor.rb +318 -0
  41. data/lib/solis/store/sparql/client/query.rb +35 -0
  42. data/lib/solis/store/sparql/client.rb +41 -0
  43. data/lib/solis/version.rb +3 -0
  44. data/lib/solis.rb +13 -0
  45. data/solis.gemspec +50 -0
  46. metadata +304 -0
@@ -0,0 +1,133 @@
1
+ module Solis
2
+ module QueryFilter
3
+ def filter(params)
4
+ if params.key?(:language)
5
+ @language = params[:language].nil? || params[:language].blank? ? nil : params[:language]
6
+ end
7
+
8
+ parsed_filters = {values: ["VALUES ?type {#{target_class}}"], concepts: ['?concept a ?type .'] }
9
+ if params.key?(:filters)
10
+ filters = params[:filters]
11
+
12
+ if filters.is_a?(String)
13
+ parsed_filters[:concepts] << concept_by_string(filters)
14
+ else
15
+ i=0
16
+ filters.each do |key, value|
17
+ if is_key_a_shacl_entity?(key) #nodeKind sh:URI
18
+ parsed_filters[:values] << values_for(key, value)
19
+ parsed_filters[:concepts] << concepts_for(key)
20
+ else
21
+ parsed_filters[:concepts] << other_stuff(key, value, i)
22
+ end
23
+ i+=1
24
+ end
25
+ end
26
+ end
27
+
28
+ @filter = parsed_filters
29
+ self
30
+ rescue StandardError => e
31
+ LOGGER.error(e.message)
32
+ LOGGER.error(e.backtrace.join("\n"))
33
+ raise Error::GeneralError, e.message
34
+ end
35
+
36
+ private
37
+
38
+ def is_key_a_shacl_entity?(key)
39
+ @metadata[:attributes].key?(key.to_s) && @metadata[:attributes][key.to_s][:node_kind] && @metadata[:attributes][key.to_s][:node_kind]&.vocab == RDF::Vocab::SH
40
+ end
41
+
42
+ def values_for(key, value, i=0)
43
+ values_model = @model.class.graph.shape_as_model(@metadata[:attributes][key.to_s][:datatype].to_s)&.new
44
+ if value.is_a?(Hash)
45
+ other_stuff(key, value, i)
46
+ else
47
+ "VALUES ?filter_by_#{key}_id{#{value.split(',').map {|v| target_class_by_model(values_model, v)}.join(' ')}}" if values_model
48
+ end
49
+ end
50
+
51
+ def concepts_for(key)
52
+ filter_predicate = URI.parse(@metadata[:attributes][key.to_s][:path])
53
+ filter_predicate.path = "/#{key.to_s.downcase}"
54
+
55
+ "?concept <#{filter_predicate.to_s}> ?filter_by_#{key}_id ."
56
+ end
57
+
58
+ def concept_by_string(filters)
59
+ contains = filters.split(',').map { |m| "CONTAINS(LCASE(str(?__search)), LCASE(\"#{m}\"))" }.join(' || ')
60
+ "?concept (#{@metadata[:attributes].map { |_, m| "(<#{m[:path]}>)" }.join('|')}) ?__search FILTER CONTAINS(LCASE(str(?__search)), LCASE(\"#{contains}\")) ."
61
+ end
62
+
63
+ def other_stuff(key, value, i)
64
+ filter = ''
65
+ unless value.is_a?(Hash) && value.key?(:value)
66
+ #TODO: only handles 'eq' for now
67
+ value = { value: value.is_a?(Array) ? value.first : value, operator: '=', is_not: false }
68
+ end
69
+
70
+ if value[:value].is_a?(String)
71
+ contains = value[:value].split(',').map { |m| "CONTAINS(LCASE(str(?__search#{i})), LCASE(\"#{m}\"))" }.join(' || ')
72
+ else
73
+ value[:value] = [value[:value]] unless value[:value].is_a?(Array)
74
+ value[:value].flatten!
75
+ contains = value[:value].map { |m| m.is_a?(String) ? "CONTAINS(LCASE(str(?__search#{i})), LCASE(\"#{m}\"))" : next }.join(' || ')
76
+ end
77
+
78
+ metadata = @metadata[:attributes][key.to_s]
79
+ if metadata
80
+ if metadata[:path] =~ %r{/id$}
81
+ if value[:value].is_a?(String)
82
+ contains = value[:value].split(',').map { |m| "\"#{m}\"" }.join(',')
83
+ else
84
+ value[:value].flatten!
85
+ contains = value[:value].map { |m| "\"#{m}\"" }.join(',')
86
+ end
87
+ if value[:is_not]
88
+ value[:value].each do |v|
89
+ v=normalize_string(v)
90
+ filter = "filter( !exists {?concept <#{@model.class.graph_name}id> \"#{v}\"})"
91
+ end
92
+ else
93
+ filter = "?concept <#{@model.class.graph_name}id> ?__search FILTER (?__search IN(#{contains})) .\n"
94
+ end
95
+ else
96
+ datatype = ''
97
+ datatype = "^^<http://www.w3.org/2001/XMLSchema#boolean>" if metadata[:datatype].eql?(:boolean)
98
+
99
+ if ["=", "<", ">"].include?(value[:operator])
100
+ not_operator = value[:is_not] ? '!' : ''
101
+ value[:value].each do |v|
102
+ if metadata[:datatype_rdf].eql?('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')
103
+ filter = "?concept <#{metadata[:path]}> ?__search#{i} "
104
+ filter += "FILTER(langMatches( lang(?__search#{i}), \"#{v[:"@language"]}\" )). "
105
+ search_for = v[:"@value"].is_a?(Array) ? v[:"@value"].first : v[:"@value"]
106
+ search_for = normalize_string(search_for)
107
+ filter += "FILTER(str(?__search#{i}) #{not_operator}#{value[:operator]} \"#{search_for}\"#{datatype}) .\n"
108
+ else
109
+ v=normalize_string(v)
110
+ filter = "?concept <#{metadata[:path]}> ?__search#{i} FILTER(?__search#{i} #{not_operator}#{value[:operator]} \"#{v}\"#{datatype}) .\n"
111
+ end
112
+
113
+
114
+ end
115
+ else
116
+ filter = "?concept <#{metadata[:path]}> ?__search#{i} FILTER(#{contains.empty? ? '""' : contains}) .\n"
117
+ end
118
+ end
119
+ end
120
+
121
+ filter
122
+ end
123
+
124
+ def normalize_string(string)
125
+ if string.is_a?(String)
126
+ string.gsub(/\t/, '\t').gsub(/\n/,'\n').gsub(/\r/,'\r').gsub(/\f/,'\f').gsub(/"/,'\"').gsub(/'/,'\'').gsub(/\\/,'\\')
127
+ else
128
+ string
129
+ end
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,97 @@
1
+ require 'solis/store/sparql/client'
2
+ require 'solis/config_file'
3
+
4
+ class Solis::Query::Runner
5
+ def self.run(entity, query)
6
+ result = {}
7
+ context = JSON.parse %(
8
+ {
9
+ "@context": {
10
+ "@vocab": "#{Solis::Options.instance.get[:graph_name]}",
11
+ "id": "@id"
12
+ },
13
+ "@type": "#{entity}",
14
+ "@embed": "@always"
15
+ }
16
+ )
17
+
18
+ c = Solis::Store::Sparql::Client.new(Solis::Options.instance.get[:sparql_endpoint], Solis::Options.instance.get[:graph_name])
19
+ r = c.query(query)
20
+ if r.is_a?(SPARQL::Client)
21
+ g = RDF::Graph.new
22
+ t = r.query('select * where{?s ?p ?o}')
23
+ t.each do |s|
24
+ g << [s.s, s.p, s.o]
25
+ end
26
+
27
+ framed = nil
28
+ JSON::LD::API.fromRDF(g) do |e|
29
+ framed = JSON::LD::API.frame(e, context)
30
+ end
31
+ result = sanitize_result(framed)
32
+ else
33
+ t = []
34
+ r.each do |s|
35
+ t << s.to_h
36
+ end
37
+ result = sanitize_result({'@graph' => t})
38
+ end
39
+ result
40
+ rescue StandardError => e
41
+ puts e.message
42
+ raise e
43
+ end
44
+
45
+ private
46
+
47
+ def self.sanitize_result(framed)
48
+ data = framed&.key?('@graph') ? framed['@graph'] : [framed]
49
+
50
+ data.map do |d|
51
+ d.delete_if { |e| e =~ /^@/ }
52
+ if d.is_a?(Hash)
53
+ new_d = {}
54
+ d.each do |k,v|
55
+ if v.is_a?(Hash)
56
+ if v.key?('@type')
57
+ type = v['@type']
58
+ if v.key?('@value')
59
+ value = v['@value']
60
+ case type
61
+ when "http://www.w3.org/2001/XMLSchema#dateTime"
62
+ value = DateTime.parse(value)
63
+ when "http://www.w3.org/2001/XMLSchema#date"
64
+ value = Date.parse(value)
65
+ when "http://www.w3.org/2006/time#DateTimeInterval"
66
+ value = ISO8601::TimeInterval.parse(value)
67
+ end
68
+ v = value
69
+ end
70
+ v = sanitize_result(v) if v.is_a?(Hash)
71
+ end
72
+ new_d[k] = v.class.method_defined?(:value) ? v.value : v
73
+ elsif v.is_a?(Array) #todo: make recursive
74
+ new_d[k] = []
75
+ v.each do |vt|
76
+ if vt.is_a?(Hash)
77
+ if vt.key?('@value')
78
+ new_d[k] << vt['@value']
79
+ else
80
+ new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
81
+ end
82
+ else
83
+ new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
84
+ end
85
+ end
86
+ new_d[k].flatten!
87
+ else
88
+ new_d[k] = v.class.method_defined?(:value) ? v.value : v
89
+ end
90
+ end
91
+ d = new_d
92
+ end
93
+
94
+ d
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moneta'
4
+ require 'solis/query/filter'
5
+ require 'solis/query/construct'
6
+ require 'solis/query/run'
7
+ require 'uuidtools'
8
+
9
+ module Solis
10
+ class Query
11
+ include Enumerable
12
+ include Solis::QueryFilter
13
+
14
+ def self.run(entity, query)
15
+ Solis::Query::Runner.run(entity, query)
16
+ end
17
+
18
+ def self.run_construct_with_file(filename, id_name, entity, ids, from_cache = '1')
19
+ f = File.read(filename)
20
+ run_construct(f, id_name, entity, ids, from_cache)
21
+ end
22
+
23
+ def self.uuid(key)
24
+ UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, key).to_s
25
+ end
26
+
27
+ def self.run_construct(query, id_name, entity, ids, from_cache = '1')
28
+ raise 'Please supply one or more uuid\'s' if ids.nil? || ids.empty?
29
+
30
+ result = {}
31
+
32
+ key = uuid("#{entity}-#{ids}")
33
+
34
+ if result.nil? || result.empty? || (from_cache.eql?('0'))
35
+ ids = ids.split(',') if ids.is_a?(String)
36
+ ids = [ids] unless ids.is_a?(Array)
37
+ ids = ids.map do |m|
38
+ if URI(m).class.eql?(URI::Generic)
39
+ "<#{Solis::Options.instance.get[:graph_name]}#{entity.tableize}/#{m}>"
40
+ else
41
+ "<#{m}>"
42
+ end
43
+ end
44
+ ids = ids.join(" ")
45
+
46
+
47
+ q = query.gsub(/{ ?{ ?VALUES ?} ?}/, "VALUES ?#{id_name} { #{ids} }")
48
+
49
+ result = Solis::Query.run(entity, q)
50
+ end
51
+ result
52
+ rescue StandardError => e
53
+ puts e.message
54
+ raise e
55
+ end
56
+
57
+ def initialize(model)
58
+ @construct_cache = File.absolute_path(Solis::Options.instance.get[:cache])
59
+ @model = model
60
+ @shapes = @model.class.shapes
61
+ @metadata = @model.class.metadata
62
+ @sparql_endpoint = @model.class.sparql_endpoint
63
+ @sparql_client = SPARQL::Client.new(@sparql_endpoint, graph: @model.class.graph_name)
64
+ @filter = {values: ["VALUES ?type {#{target_class}}"], concepts: ['?concept a ?type .'] }
65
+ @sort = 'ORDER BY ?s'
66
+ @sort_select = ''
67
+ @language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || 'en'
68
+ @query_cache = Moneta.new(:HashFile, dir: @construct_cache)
69
+ end
70
+
71
+ def each(&block)
72
+ data = query
73
+ return unless data.methods.include?(:each)
74
+ data.each(&block)
75
+ rescue StandardError => e
76
+ message = "Unable to get next record: #{e.message}"
77
+ LOGGER.error(message)
78
+ raise Error::CursorError, message
79
+ end
80
+
81
+ def sort(params)
82
+ @sort = ''
83
+ @sort_select = ''
84
+ if params.key?(:sort)
85
+ i = 0
86
+ params[:sort].each do |attribute, direction|
87
+ path = @model.class.metadata[:attributes][attribute.to_s][:path]
88
+ @sort_select += "optional {\n" if @model.class.metadata[:attributes][attribute.to_s][:mincount] == 0
89
+ @sort_select += "?concept <#{path}> ?__#{attribute} . "
90
+ @sort_select += "}\n" if @model.class.metadata[:attributes][attribute.to_s][:mincount] == 0
91
+ @sort += ',' if i.positive?
92
+ @sort += "#{direction.to_s.upcase}(?__#{attribute})"
93
+ i += 1
94
+ end
95
+
96
+ @sort = "ORDER BY #{@sort}" if i.positive?
97
+ end
98
+
99
+ self
100
+ end
101
+
102
+ def paging(params = {})
103
+ current_page = params[:current_page] || 1
104
+ per_page = params[:per_page] || 10
105
+
106
+ @offset = 0
107
+ @offset = current_page * per_page if current_page > 1
108
+ @limit = per_page
109
+ self
110
+ end
111
+
112
+ def count
113
+ sparql_client = @sparql_client
114
+ if model_construct?
115
+ sparql_client = Solis::Query::Construct.new(@model).run
116
+ end
117
+
118
+ relationship = ''
119
+ core_query = core_query(relationship)
120
+ count_query = core_query.gsub(/SELECT .* WHERE/, 'SELECT (COUNT(distinct ?concept) as ?count) WHERE')
121
+
122
+ result = sparql_client.query(count_query)
123
+ solution = result.first
124
+ solution.nil? ? 0 : solution[:count].object || 0
125
+ end
126
+
127
+ private
128
+
129
+ def model_construct?
130
+ File.exist?("#{ConfigFile.path}/constructs/#{@model.name.tableize.singularize}.sparql")
131
+ end
132
+
133
+ def target_class
134
+ # descendants = ObjectSpace.each_object(Class).select { |klass| klass < @model.class }.map { |m| m.class.name.eql?('Class') ? m.superclass : m }.map { |m| m.metadata[:target_class].value }
135
+ descendants = ObjectSpace.each_object(Class).select { |klass| klass < @model.class }.reject { |m| m.metadata.nil? }.map { |m| m.metadata[:target_class].value }
136
+ descendants << @model.class.metadata[:target_class].value
137
+ descendants.map { |m| "<#{m}>" }.join(' ')
138
+ end
139
+
140
+ def target_class_by_model(model, id=nil)
141
+ descendants = ObjectSpace.each_object(Class).select { |klass| klass < model.class }.reject { |m| m.metadata.nil? }.map { |m| m.metadata[:target_class].value.tableize }
142
+ descendants << model.class.metadata[:target_class].value.tableize
143
+ if id.nil?
144
+ descendants.map { |m| "<#{m}>" }.join(' ')
145
+ else
146
+ descendants.map { |m| "<#{m}/#{id}>" }.join(' ')
147
+ end
148
+ end
149
+
150
+
151
+ def query(options = {})
152
+ limit = @limit || 10
153
+ offset = @offset || 0
154
+
155
+ sparql_client = model_construct? ? Solis::Query::Construct.new(@model).run : @sparql_client
156
+ #sparql_client = model_construct? ? SPARQL::Client.new(run_construct) : @sparql_client
157
+
158
+ relationship = ''
159
+ if options.key?(:relationship)
160
+ link = "#{@model.class.graph_name}#{ActiveSupport::Inflector.pluralize(@klass.name).downcase}/#{id}"
161
+ path = @model.class.metadata[:attributes][options[:relationship]][:path]
162
+ relationship = "<#{link}> <#{path}> ?o ."
163
+ end
164
+
165
+ core_query = core_query(relationship)
166
+ if core_query =~ /IN\((.*?)\)/
167
+ #limit = $1.gsub('"','').split(',').length
168
+ else
169
+ core_query += " LIMIT #{limit} OFFSET #{offset}"
170
+ end
171
+
172
+ query = %(
173
+ #{prefixes}
174
+ SELECT ?s ?p ?o WHERE {
175
+ ?s ?p ?o
176
+ {
177
+ #{core_query}
178
+ }
179
+ #{language_filter}
180
+ }
181
+ order by ?s
182
+ )
183
+
184
+ Solis::LOGGER.info(query) if ConfigFile[:debug]
185
+
186
+ query_key = "#{@model.name}-#{Digest::MD5.hexdigest(query)}"
187
+
188
+ result = nil
189
+
190
+ from_cache = Graphiti.context[:object].from_cache || '1'
191
+ if @query_cache.key?(query_key) && from_cache.eql?('1')
192
+ result = @query_cache[query_key]
193
+ Solis::LOGGER.info("CACHE: from #{query_key}")# if ConfigFile[:debug]
194
+ else
195
+ result = graph_to_object(sparql_client.query(query))
196
+ @query_cache[query_key] = result unless result.nil? || result.empty?
197
+ Solis::LOGGER.info("CACHE: to #{query_key}")# if ConfigFile[:debug]
198
+ end
199
+
200
+ result
201
+ rescue StandardError => e
202
+ Solis::LOGGER.error(e.message)
203
+ Solis::LOGGER.error(e.backtrace.join("\n"))
204
+ end
205
+
206
+ def language_filter
207
+ if @language.nil?
208
+ ''
209
+ else
210
+ %(
211
+ filter (
212
+ !isLiteral(?o) ||
213
+ langmatches(lang(?o), "#{@language}")
214
+ || (langmatches(lang(?o), "") && not exists {
215
+ ?s ?p ?other.
216
+ filter(isLiteral(?other) && langmatches(lang(?other), "#{@language}"))
217
+ }))
218
+ )
219
+ end
220
+ end
221
+
222
+ def core_query(relationship)
223
+ core_query = %(
224
+ SELECT distinct (?concept AS ?s) WHERE {
225
+ #{@filter[:values].join("\n")}
226
+ ?concept ?role ?objects.
227
+ #{relationship}
228
+ #{@filter[:concepts].join("\n")}
229
+
230
+ #{@sort_select}
231
+ }
232
+ #{@sort}
233
+ )
234
+ end
235
+
236
+ def prefixes
237
+ "
238
+ PREFIX sh: <http://www.w3.org/ns/shacl#>
239
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
240
+ PREFIX schema: <http://schema.org/>
241
+ PREFIX rdfv: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
242
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
243
+ PREFIX #{@model.class.graph_prefix}: <#{@model.class.graph_name}>"
244
+ end
245
+
246
+ def graph_to_object(solutions)
247
+ return [] if solutions.empty?
248
+ target_class = @model.class.metadata[:target_class].value.split('/').last
249
+ result = []
250
+ record_uri = ''
251
+
252
+ begin
253
+ # solutions.sort! { |x, y| x.s.value <=> y.s.value }.map { |m| m.s.value }
254
+ solution_types = solutions.dup.filter!(p: RDF::RDFV.type)
255
+ solution_types.each do |type|
256
+ solution_model = @model.class.graph.shape_as_model(type.o.value.split('/').last)
257
+ data = {}
258
+ statements = solutions.dup.filter!(s: type.s)
259
+ statements.each do |statement|
260
+ next if statement.p.eql?(RDF::RDFV.type)
261
+
262
+ begin
263
+ record_uri = statement.s.value
264
+ attribute = statement.p.value.split('/').last.underscore
265
+
266
+ if statement.o.valid?
267
+ if statement.o.is_a?(RDF::URI)
268
+ object = statement.o.canonicalize.value
269
+ else
270
+ object = statement.o.canonicalize.object
271
+ end
272
+ else
273
+ object = Integer(statement.o.value) if Integer(statement.o.value) rescue nil
274
+ object = Float(statement.o.value) if object.nil? && Float(statement.o.value) rescue nil
275
+ object = statement.o.value if object.nil?
276
+ end
277
+
278
+ begin
279
+ datatype = RDF::Vocabulary.find_term(@model.class.metadata[:attributes][attribute][:datatype_rdf])
280
+ if RDF::Literal(statement.o).datatype.value != datatype
281
+ if statement.o.is_a?(RDF::URI)
282
+ object = RDF::Literal.new(statement.o.canonicalize.value, datatype: RDF::URI).object
283
+ else
284
+ object = RDF::Literal.new(statement.o.canonicalize.object, datatype: datatype).object
285
+ end
286
+ end
287
+ rescue StandardError => e
288
+ if object.is_a?(Hash)
289
+ object = if object.key?(:fragment) && !object[:fragment].nil?
290
+ "#{object[:path]}##{object[:fragment]}"
291
+ else
292
+ object[:path]
293
+ end
294
+ end
295
+ end
296
+
297
+ # fix non matching attributes by data type
298
+ if solution_model.metadata[:attributes][attribute].nil?
299
+ candidates = solution_model.metadata[:attributes].select { |_k, s| s[:class] == statement.p }.keys - data.keys
300
+ attribute = candidates.first unless candidates.empty?
301
+ end
302
+
303
+ begin
304
+ unless solution_model.metadata[:attributes][attribute][:node_kind].nil?
305
+ node_class = solution_model.metadata[:attributes][attribute][:class].value.split('/').last
306
+ object = solution_model.graph.shape_as_model(node_class).new({ id: object.split('/').last })
307
+ end
308
+ rescue StandardError => e
309
+ puts e.message
310
+ end
311
+
312
+ if data.key?(attribute) # attribute exists
313
+ raise "Cardinality error, max = #{solution_model.metadata[:attributes][attribute][:maxcount]}" if solution_model.metadata[:attributes][attribute][:maxcount] == 0
314
+ if solution_model.metadata[:attributes][attribute][:maxcount] == 1 && data.key?(attribute)
315
+ raise "Cardinality error, max = #{solution_model.metadata[:attributes][attribute][:maxcount]}"
316
+ elsif solution_model.metadata[:attributes][attribute][:maxcount] == 1
317
+ data[attribute] = object
318
+ else
319
+ data[attribute] = [data[attribute]] unless data[attribute].is_a?(Array)
320
+ data[attribute] << object
321
+ end
322
+ else
323
+ if solution_model.metadata[:attributes][attribute][:maxcount].nil? || solution_model.metadata[:attributes][attribute][:maxcount] > 1
324
+ data[attribute] = [object]
325
+ else
326
+ data[attribute] = object
327
+ end
328
+ end
329
+ rescue StandardError => e
330
+ puts e.backtrace.first
331
+ Solis::LOGGER.error("#{record_uri} - graph_to_object - #{attribute} - #{e.message}")
332
+ g = RDF::Graph.new
333
+ g << [statement.s, statement.p, statement.o]
334
+ Solis::LOGGER.error(g.dump(:ttl).to_s)
335
+ end
336
+ end
337
+ result << solution_model.new(data) unless data.empty?
338
+ end
339
+ rescue StandardError => e
340
+ Solis::LOGGER.error("#{record_uri} - graph_to_object - #{e.message}")
341
+ end
342
+
343
+ # result << solution_model.new(data) unless data.empty?
344
+ result
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'sparql_adaptor'
2
+ require_relative 'config_file'
3
+ require_relative 'options'
4
+
5
+ module Solis
6
+ class Resource < ::Graphiti::Resource
7
+
8
+ self.abstract_class = true
9
+ self.adapter = Solis::SparqlAdaptor
10
+ self.endpoint_namespace = Solis::Options.instance.get[:base_path] rescue ''
11
+ self.validate_endpoints = true
12
+
13
+ def self.sparql_endpoint
14
+ @sparql_endpoint
15
+ end
16
+
17
+ def self.sparql_endpoint=(sparql_endpoint)
18
+ @sparql_endpoint = sparql_endpoint
19
+ end
20
+ end
21
+
22
+ class NoopEndpoint
23
+ def initialize(path, action)
24
+ @path = path
25
+ @action = action
26
+ end
27
+
28
+ def sideload_allowlist
29
+ @allow_list
30
+ end
31
+
32
+ def sideload_allowlist=(val)
33
+ @allow_list = JSONAPI::IncludeDirective.new(val).to_hash
34
+ super(@allow_list)
35
+ end
36
+ end
37
+ end