solis 0.64.0

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