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
@@ -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
|
data/lib/solis/query.rb
ADDED
@@ -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
|