activerdf 1.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.
- data/LICENSE +504 -0
- data/README +28 -0
- data/Rakefile +69 -0
- data/lib/active_rdf/federation/active_rdf_adapter.rb +18 -0
- data/lib/active_rdf/federation/connection_pool.rb +114 -0
- data/lib/active_rdf/federation/federation_manager.rb +80 -0
- data/lib/active_rdf/objectmanager/namespace.rb +77 -0
- data/lib/active_rdf/objectmanager/object_manager.rb +123 -0
- data/lib/active_rdf/objectmanager/resource.rb +325 -0
- data/lib/active_rdf/queryengine/query.rb +166 -0
- data/lib/active_rdf/queryengine/query2jars2.rb +27 -0
- data/lib/active_rdf/queryengine/query2sparql.rb +51 -0
- data/lib/active_rdf.rb +48 -0
- data/lib/active_rdf_helpers.rb +12 -0
- data/lib/active_rdf_log.rb +43 -0
- data/test/common.rb +91 -0
- data/test/federation/test_connection_pool.rb +76 -0
- data/test/federation/test_federation_manager.rb +148 -0
- data/test/objectmanager/test_namespace.rb +65 -0
- data/test/objectmanager/test_object_manager.rb +52 -0
- data/test/objectmanager/test_resource_reading.rb +83 -0
- data/test/objectmanager/test_resource_writing.rb +26 -0
- data/test/queryengine/test_query.rb +51 -0
- data/test/queryengine/test_query2jars2.rb +51 -0
- data/test/queryengine/test_query2sparql.rb +51 -0
- data/test/queryengine/test_query_engine.rb +52 -0
- data/test/test_adapters.rb +52 -0
- data/test/test_person_data.nt +28 -0
- data/tools/rakehelp.rb +103 -0
- metadata +89 -0
@@ -0,0 +1,325 @@
|
|
1
|
+
# Represents an RDF resource and manages manipulations of that resource,
|
2
|
+
# including data lookup (e.g. eyal.age), data updates (e.g. eyal.age=20),
|
3
|
+
# class-level lookup (Person.find_by_name 'eyal'), and class-membership
|
4
|
+
# (eyal.class ...Person)
|
5
|
+
#
|
6
|
+
# Author:: Eyal Oren
|
7
|
+
# Copyright:: (c) 2005-2006
|
8
|
+
# License:: LGPL
|
9
|
+
|
10
|
+
require 'active_rdf'
|
11
|
+
require 'objectmanager/object_manager'
|
12
|
+
require 'objectmanager/namespace'
|
13
|
+
require 'queryengine/query'
|
14
|
+
|
15
|
+
# TODO: add unit test to validate class construction and queries on them
|
16
|
+
module RDFS
|
17
|
+
class RDFS::Resource
|
18
|
+
# adding accessor to the class uri:
|
19
|
+
# the uri of the rdf resource being represented by this class
|
20
|
+
class << self
|
21
|
+
attr_accessor :class_uri
|
22
|
+
end
|
23
|
+
|
24
|
+
# uri of the resource (for instances of this class: rdf resources)
|
25
|
+
attr_reader :uri
|
26
|
+
|
27
|
+
# creates new resource representing an RDF resource
|
28
|
+
def initialize uri
|
29
|
+
raise ActiveRdfError, "creating resource <#{uri}>" unless uri.is_a?(String)
|
30
|
+
|
31
|
+
# $log.debug "RDFS::Resource new: initializing new Resource with #{uri}"
|
32
|
+
@uri = uri
|
33
|
+
end
|
34
|
+
|
35
|
+
# setting our own class uri to rdfs:resource
|
36
|
+
# (has to be done after defining our RDFS::Resource.new
|
37
|
+
# because it cannot be found in Namespace.lookup otherwise)
|
38
|
+
self.class_uri = Namespace.lookup(:rdfs, :Resource)
|
39
|
+
|
40
|
+
##### ######
|
41
|
+
##### start of instance-level code
|
42
|
+
##### ######
|
43
|
+
|
44
|
+
# a resource is same as another if they both represent the same uri
|
45
|
+
def ==(other)
|
46
|
+
if other.respond_to?(:uri)
|
47
|
+
other.uri == self.uri
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method 'eql?','=='
|
53
|
+
|
54
|
+
# overriding hash to use uri.hash
|
55
|
+
# needed for array.uniq
|
56
|
+
def hash
|
57
|
+
uri.hash
|
58
|
+
end
|
59
|
+
|
60
|
+
# overriding sort based on uri
|
61
|
+
def <=>(other)
|
62
|
+
uri <=> other.uri
|
63
|
+
end
|
64
|
+
|
65
|
+
##### #####
|
66
|
+
##### class level methods #####
|
67
|
+
##### #####
|
68
|
+
|
69
|
+
# returns the predicates that have this resource as their domain (applicable
|
70
|
+
# predicates for this resource)
|
71
|
+
def Resource.predicates
|
72
|
+
domain = Namespace.lookup(:rdfs, :domain)
|
73
|
+
Query.new.distinct(:p).where(:p, domain, class_uri).execute || []
|
74
|
+
end
|
75
|
+
|
76
|
+
# manages invocations such as Person.find_by_name
|
77
|
+
def Resource.method_missing(method, *args)
|
78
|
+
method_name = method.to_s
|
79
|
+
|
80
|
+
$log.debug "RDFS::Resource: method_missing on class: called with method name #{method}"
|
81
|
+
|
82
|
+
# extract predicates on which to match
|
83
|
+
# e.g. find_by_name, find_by_name_and_age
|
84
|
+
if match = /find_by_(.+)/.match(method_name)
|
85
|
+
# find searched attributes, e.g. name, age
|
86
|
+
attributes = match[1].split('_and_')
|
87
|
+
|
88
|
+
# get list of possible predicates for this class
|
89
|
+
possible_predicates = predicates
|
90
|
+
|
91
|
+
# build query looking for all resources with the given parameters
|
92
|
+
query = Query.new.distinct(:s)
|
93
|
+
|
94
|
+
# add where clause for each attribute-value pair,
|
95
|
+
# looking into possible_predicates to figure out
|
96
|
+
# which full-URI to use for each given parameter (heuristic)
|
97
|
+
|
98
|
+
attributes.each_with_index do |atr,i|
|
99
|
+
possible_predicates.each do |pred|
|
100
|
+
query.where(:s, pred, args[i]) if Namespace.localname(pred) == atr
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# execute query
|
105
|
+
$log.debug "RDFS::Resource: method_missing on class: executing query: #{query}"
|
106
|
+
return query.execute
|
107
|
+
end
|
108
|
+
|
109
|
+
# otherwise, if no match found, raise NoMethodError (in superclass)
|
110
|
+
$log.warn 'RDFS::Resource: method_missing on class: method not matching find_by_*'
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
# returns array of all instances of this class (e.g. Person.find_all)
|
115
|
+
# (always returns collection)
|
116
|
+
def Resource.find_all
|
117
|
+
query = Query.new.distinct(:s).where(:s, Namespace.lookup(:rdf,:type), class_uri)
|
118
|
+
if block_given?
|
119
|
+
query.execute do |resource|
|
120
|
+
yield resource
|
121
|
+
end
|
122
|
+
else
|
123
|
+
query.execute(:flatten => false)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
##### #####
|
128
|
+
##### instance level methods #####
|
129
|
+
##### #####
|
130
|
+
|
131
|
+
# manages invocations such as eyal.age
|
132
|
+
def method_missing(method, *args)
|
133
|
+
# possibilities:
|
134
|
+
# 1. eyal.age is a property of eyal (triple exists <eyal> <age> "30")
|
135
|
+
# evidence: eyal age ?a, ?a is not nil (only if value exists)
|
136
|
+
# action: return ?a
|
137
|
+
#
|
138
|
+
# 2. eyal's class is in domain of age, but does not have value for eyal
|
139
|
+
# explain: eyal is a person and some other person (not eyal) has an age
|
140
|
+
# evidence: eyal type ?c, age domain ?c
|
141
|
+
# action: return nil
|
142
|
+
#
|
143
|
+
# 3. eyal.age = 30 (setting a value for a property)
|
144
|
+
# explain: eyal has (or could have) a value for age, and we update that value
|
145
|
+
# complication: we need to find the full URI for age (by looking at
|
146
|
+
# possible predicates to use
|
147
|
+
# evidence: eyal age ?o (eyal has a value for age now, we're updating it)
|
148
|
+
# evidence: eyal type ?c, age domain ?c (eyal could have a value for age, we're setting it)
|
149
|
+
# action: add triple (eyal, age, 30), return 30
|
150
|
+
#
|
151
|
+
# 4. eyal.age is a custom-written method in class Person
|
152
|
+
# evidence: eyal type ?c, ?c.methods includes age
|
153
|
+
# action: inject age into eyal and invoke
|
154
|
+
|
155
|
+
# maybe change order in which to check these, checking (4) is probably
|
156
|
+
# cheaper than (1)-(2) but (1) and (2) are probably more probable (getting
|
157
|
+
# attribute values over executing custom methods)
|
158
|
+
|
159
|
+
$log.debug "RDFS::Resource: method_missing on instance: called with method name #{method}"
|
160
|
+
|
161
|
+
# are we doing an update or not?
|
162
|
+
# checking if method ends with '='
|
163
|
+
|
164
|
+
if method.to_s[-1..-1] == '='
|
165
|
+
methodname = method.to_s[0..-2]
|
166
|
+
update = true
|
167
|
+
else
|
168
|
+
methodname = method.to_s
|
169
|
+
update = false
|
170
|
+
end
|
171
|
+
|
172
|
+
candidates = if update
|
173
|
+
class_level_predicates
|
174
|
+
else
|
175
|
+
direct_predicates
|
176
|
+
end
|
177
|
+
|
178
|
+
# checking possibility (1) and (3)
|
179
|
+
candidates.each do |pred|
|
180
|
+
if Namespace.localname(pred) == methodname
|
181
|
+
# found a property invocation of eyal: option 1) or 2)
|
182
|
+
# query execution will return either the value for the predicate (1)
|
183
|
+
# or nil (2)
|
184
|
+
if update
|
185
|
+
# TODO: delete old value if overwriting
|
186
|
+
# FederiationManager.delete(self, pred, nil)
|
187
|
+
|
188
|
+
# handling eyal.friends = [armin, andreas] --> expand array values
|
189
|
+
args.each do |value|
|
190
|
+
FederationManager.add(self, pred, value)
|
191
|
+
end
|
192
|
+
return args
|
193
|
+
else
|
194
|
+
# look into args, if it contains a hash with {:array => true} then
|
195
|
+
# we should not flatten the query results
|
196
|
+
return get_property_value(pred, args)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
raise ActiveRdfError, "could not set #{methodname} to #{args}: no suitable predicate found. Maybe you are missing some shcema information?" if update
|
202
|
+
|
203
|
+
# get/set attribute value did not succeed, so checking option (2) and (4)
|
204
|
+
|
205
|
+
# checking possibility (2), it is not handled correctly above since we use
|
206
|
+
# direct_predicates instead of class_level_predicates. If we didn't find
|
207
|
+
# anything with direct_predicates, we need to try the
|
208
|
+
# class_level_predicates. Only if we don't find either, we
|
209
|
+
# throw "method_missing"
|
210
|
+
candidates = class_level_predicates
|
211
|
+
|
212
|
+
# if any of the class_level candidates fits the sought method, then we
|
213
|
+
# found situation (2), so we return nil or [] depending on the {:array =>
|
214
|
+
# true} value
|
215
|
+
if candidates.any?{|c| Namespace.localname(c) == methodname}
|
216
|
+
return_ary = args[0][:array] if args[0].is_a? Hash
|
217
|
+
if return_ary
|
218
|
+
return []
|
219
|
+
else
|
220
|
+
return nil
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# checking possibility (4)
|
226
|
+
# TODO: implement search strategy to select in which class to invoke
|
227
|
+
# e.g. if to_s defined in Resource and in Person we should use Person
|
228
|
+
$log.debug "RDFS::Resource: method_missing on instance: branch selected: execution of custom class method"
|
229
|
+
self.class.each do |klass|
|
230
|
+
if klass.instance_methods.include?(method.to_s)
|
231
|
+
_dup = klass.new(uri)
|
232
|
+
return _dup.send(method,*args)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# if none of the three possibilities work out, we don't know this method
|
237
|
+
# invocation, but we don't want to throw NoMethodError, instead we return
|
238
|
+
# nil, so that eyal.age does not raise error, but returns nil. (in RDFS,
|
239
|
+
# we are never sure that eyal cannot have an age, we just dont know the
|
240
|
+
# age right now)
|
241
|
+
nil
|
242
|
+
end
|
243
|
+
|
244
|
+
# returns classes to which this resource belongs (according to rdf:type)
|
245
|
+
def class
|
246
|
+
types.collect do |type|
|
247
|
+
ObjectManager.construct_class(type)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def type
|
252
|
+
get_property_value(Namespace.lookup(:rdf,:type))
|
253
|
+
end
|
254
|
+
|
255
|
+
# overrides built-in instance_of? to use rdf:type definitions
|
256
|
+
def instance_of?(klass)
|
257
|
+
self.class.include?(klass)
|
258
|
+
end
|
259
|
+
|
260
|
+
# returns all predicates that fall into the domain of the rdf:type of this
|
261
|
+
# resource
|
262
|
+
def class_level_predicates
|
263
|
+
type = Namespace.lookup(:rdf, 'type')
|
264
|
+
domain = Namespace.lookup(:rdfs, 'domain')
|
265
|
+
Query.new.distinct(:p).where(self,type,:t).where(:p, domain, :t).execute || []
|
266
|
+
end
|
267
|
+
|
268
|
+
# returns all predicates that are directly defined for this resource
|
269
|
+
def direct_predicates(distinct = true)
|
270
|
+
if distinct
|
271
|
+
q = Query.new.distinct(:p)
|
272
|
+
else
|
273
|
+
q = Query.new.select(:p)
|
274
|
+
end
|
275
|
+
q.where(self,:p, :o).execute(:flatten => false) || []
|
276
|
+
end
|
277
|
+
|
278
|
+
def property_accessors
|
279
|
+
direct_predicates.collect {|pred| Namespace.localname(pred) }
|
280
|
+
end
|
281
|
+
|
282
|
+
# returns all rdf:types of this resource
|
283
|
+
def types
|
284
|
+
type = Namespace.lookup(:rdf, :type)
|
285
|
+
|
286
|
+
# we lookup the type in the database
|
287
|
+
types = Query.new.distinct(:t).where(self,type,:t).execute(:flatten => false)
|
288
|
+
|
289
|
+
# if we dont know it, we return Resource (as toplevel)
|
290
|
+
# this should in theory actually never happen (since any node is a rdfs:Resource)
|
291
|
+
# but could happen if the subject is unknown to the database
|
292
|
+
# or if the database does not support RDFS inferencing
|
293
|
+
return [Namespace.lookup(:rdfs,"Resource")] if types.empty?
|
294
|
+
return types
|
295
|
+
end
|
296
|
+
|
297
|
+
# alias include? to ==, so that you can do paper.creator.include?(eyal)
|
298
|
+
# without worrying whether paper.creator is single- or multi-valued
|
299
|
+
alias include? ==
|
300
|
+
|
301
|
+
# returns uri of resource, can be overridden in subclasses
|
302
|
+
def to_s
|
303
|
+
"resource: #{uri}"
|
304
|
+
end
|
305
|
+
|
306
|
+
def label(*args)
|
307
|
+
label = get_property_value(Namespace.lookup(:rdfs,:label)) || Namespace.localname(self)
|
308
|
+
|
309
|
+
# empty labels are not useful: replace them by localname
|
310
|
+
label = Namespace.localname(self) if label.empty?
|
311
|
+
|
312
|
+
# if we have no localname, use full uri
|
313
|
+
label = uri if label.empty?
|
314
|
+
|
315
|
+
label
|
316
|
+
end
|
317
|
+
|
318
|
+
private
|
319
|
+
def get_property_value(predicate, args=[])
|
320
|
+
return_ary = args[0][:array] if args[0].is_a?(Hash)
|
321
|
+
flatten_results = !return_ary
|
322
|
+
Query.new.distinct(:o).where(self, predicate, :o).execute(:flatten => flatten_results)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Represents a query on a datasource, abstract representation of SPARQL features
|
2
|
+
# is passed to federation/adapter for execution on data
|
3
|
+
#
|
4
|
+
# Author:: Eyal Oren
|
5
|
+
# Copyright:: (c) 2005-2006
|
6
|
+
# License:: LGPL
|
7
|
+
require 'active_rdf'
|
8
|
+
require 'federation/federation_manager'
|
9
|
+
|
10
|
+
# TODO add log for every method which constitues to the query
|
11
|
+
|
12
|
+
class Query
|
13
|
+
attr_reader :select_clauses, :where_clauses, :keywords, :limits, :offsets
|
14
|
+
bool_accessor :distinct, :ask, :select, :count, :keyword
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
$log.debug "Query: initializing"
|
18
|
+
distinct = false
|
19
|
+
limit = nil
|
20
|
+
offset = nil
|
21
|
+
@select_clauses = []
|
22
|
+
@where_clauses = []
|
23
|
+
@keywords = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear_select
|
27
|
+
$log.debug "Query: clearing select query"
|
28
|
+
@select_clauses = []
|
29
|
+
distinct = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def select *s
|
33
|
+
@select = true
|
34
|
+
s.each do |e|
|
35
|
+
@select_clauses << parametrise(e)
|
36
|
+
end
|
37
|
+
# removing duplicate select clauses
|
38
|
+
@select_clauses.uniq!
|
39
|
+
$log.debug "Query: the current select clauses are: #{@select_clauses.join(', ')}"
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def ask
|
44
|
+
@ask = true
|
45
|
+
$log.debug "Query: the current select clauses are: #{@select_clauses.join(', ')}"
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def distinct *s
|
50
|
+
@distinct = true
|
51
|
+
select(*s)
|
52
|
+
end
|
53
|
+
alias_method :select_distinct, :distinct
|
54
|
+
|
55
|
+
def count *s
|
56
|
+
@count = true
|
57
|
+
select(*s)
|
58
|
+
end
|
59
|
+
|
60
|
+
def limit(i)
|
61
|
+
@limits = i
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def offset(i)
|
66
|
+
@offsets = i
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def where s,p,o
|
71
|
+
case p
|
72
|
+
when :keyword
|
73
|
+
# treat keywords in where-clauses specially
|
74
|
+
keyword_where(s,o)
|
75
|
+
else
|
76
|
+
# remove duplicate variable bindings, e.g.
|
77
|
+
# where(:s,type,:o).where(:s,type,:oo) we should remove the second clause,
|
78
|
+
# since it doesn't add anything to the query and confuses the query
|
79
|
+
# generator.
|
80
|
+
# if you construct this query manually, you shouldn't! if your select
|
81
|
+
# variable happens to be in one of the removed clauses: tough luck.
|
82
|
+
|
83
|
+
unless s.respond_to?(:uri)
|
84
|
+
unless s.class == Symbol
|
85
|
+
$log.debug "Query: where: got a Subject which is no Symbol, and no RDFS::Resource, but is instead: #{s}"
|
86
|
+
raise(ActiveRdfError, "cannot add a where clause, in which s is not a resource and not a variable")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
unless p.respond_to?(:uri)
|
90
|
+
unless p.class == Symbol
|
91
|
+
$log.debug "Query: where: got a Predicate which is no Symbol, and no RDFS::Resource, but is instead: #{p}"
|
92
|
+
raise(ActiveRdfError, "cannot add a where clause, in which s is not a resource and not a variable")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
@where_clauses << [s,p,o].collect{|arg| parametrise(arg)}
|
97
|
+
end
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# adds keyword constraint to the query. You can use all Ferret query syntax in
|
102
|
+
# the constraint (e.g. keyword_where(:s,'eyal|benjamin')
|
103
|
+
def keyword_where s,o
|
104
|
+
@keyword = true
|
105
|
+
s = parametrise(s)
|
106
|
+
if @keywords.include?(s)
|
107
|
+
@keywords[s] = @keywords[s] + ' ' + o
|
108
|
+
else
|
109
|
+
@keywords[s] = o
|
110
|
+
end
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# this is not normal behaviour, the method is implemented inside FacetNavigation
|
115
|
+
# def replace_where_clause old,new
|
116
|
+
# return unless where_clauses.includes?(old)
|
117
|
+
# where_clauses.delete(old)
|
118
|
+
# where_clauses.insert(new)
|
119
|
+
# end
|
120
|
+
|
121
|
+
# execute query on data sources
|
122
|
+
# either returns result as array
|
123
|
+
# (flattened into single value unless specified otherwise)
|
124
|
+
# or executes a block (number of block variables should be
|
125
|
+
# same as number of select variables)
|
126
|
+
#
|
127
|
+
# usage: results = query.execute
|
128
|
+
# usage: query.execute do |s,p,o| ... end
|
129
|
+
def execute(options={:flatten => true}, &block)
|
130
|
+
$log.debug "Query: executing query: #{self.inspect}"
|
131
|
+
|
132
|
+
if block_given?
|
133
|
+
FederationManager.query(self) do |*clauses|
|
134
|
+
block.call(*clauses)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
FederationManager.query(self, options)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
if ConnectionPool.read_adapters.empty?
|
143
|
+
inspect
|
144
|
+
else
|
145
|
+
ConnectionPool.read_adapters.first.translate(self)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_sp
|
150
|
+
require 'queryengine/query2sparql'
|
151
|
+
Query2SPARQL.translate(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def parametrise s
|
156
|
+
case s
|
157
|
+
when Symbol
|
158
|
+
s
|
159
|
+
#'?' + s.to_s
|
160
|
+
when RDFS::Resource
|
161
|
+
s
|
162
|
+
else
|
163
|
+
'"' + s.to_s + '"'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Translates abstract query into jars2 query
|
2
|
+
# ignores ASK queries
|
3
|
+
#
|
4
|
+
# Author:: Eyal Oren
|
5
|
+
# Copyright:: (c) 2005-2006
|
6
|
+
# License:: LGPL
|
7
|
+
require 'active_rdf'
|
8
|
+
|
9
|
+
|
10
|
+
class Query2Jars2
|
11
|
+
def self.translate(query)
|
12
|
+
str = ""
|
13
|
+
if query.select?
|
14
|
+
# concatenate each where clause using space: s p o
|
15
|
+
# and then concatenate the clauses using dot: s p o . s2 p2 o2 .
|
16
|
+
str << "#{query.where_clauses.collect{|w| w.collect{|w| '?'+w.to_s}.join(' ')}.join(" .\n")} ."
|
17
|
+
# TODO: should we maybe reverse the order on the where_clauses? it depends
|
18
|
+
# on Andreas' answer of the best order to give to jars2. Users would
|
19
|
+
# probably put the most specific stuff first, and join to get the
|
20
|
+
# interesting information. Maybe we should not touch it and let the user
|
21
|
+
# figure it out.
|
22
|
+
end
|
23
|
+
|
24
|
+
$log.debug "Query2Jars2: translated #{query} to #{str}"
|
25
|
+
return str
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# translates abstract query into SPARQL that can be executed on SPARQL-compliant data source
|
2
|
+
#
|
3
|
+
# Author:: Eyal Oren
|
4
|
+
# Copyright:: (c) 2005-2006
|
5
|
+
# License:: LGPL
|
6
|
+
require 'active_rdf'
|
7
|
+
|
8
|
+
|
9
|
+
class Query2SPARQL
|
10
|
+
def self.translate(query)
|
11
|
+
str = ""
|
12
|
+
if query.select?
|
13
|
+
distinct = query.distinct? ? "DISTINCT " : ""
|
14
|
+
select_clauses = query.select_clauses.collect{|s| construct_clause(s)}
|
15
|
+
|
16
|
+
str << "SELECT #{distinct}#{select_clauses.join(' ')} "
|
17
|
+
str << "WHERE { #{where_clauses(query)} }"
|
18
|
+
elsif query.ask?
|
19
|
+
str << "ASK { #{where_clauses(query)} }"
|
20
|
+
end
|
21
|
+
|
22
|
+
$log.debug "Query2SPARQL: translated the query to #{str}"
|
23
|
+
return str
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# concatenate each where clause using space (e.g. 's p o')
|
28
|
+
# and concatenate the clauses using dot, e.g. 's p o . s2 p2 o2 .'
|
29
|
+
def self.where_clauses(query)
|
30
|
+
where_clauses = query.where_clauses.collect do |triple|
|
31
|
+
triple.collect {|term| construct_clause(term)}.join(' ')
|
32
|
+
end
|
33
|
+
"#{where_clauses.join('. ')} ."
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.construct_clause(term)
|
37
|
+
case term
|
38
|
+
when Symbol
|
39
|
+
'?' + term.to_s
|
40
|
+
when RDFS::Resource
|
41
|
+
'<' + term.uri + '>'
|
42
|
+
else
|
43
|
+
term.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#declare the class level methods as private with these directives
|
48
|
+
private_class_method :where_clauses
|
49
|
+
private_class_method :construct_clause
|
50
|
+
|
51
|
+
end
|
data/lib/active_rdf.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Loader of ActiveRDF library
|
2
|
+
#
|
3
|
+
# Author:: Eyal Oren and Renaud Delbru
|
4
|
+
# Copyright:: (c) 2005-2006 Eyal Oren and Renaud Delbru
|
5
|
+
# License:: LGPL
|
6
|
+
|
7
|
+
# adding active_rdf subdirectory to the ruby loadpath
|
8
|
+
file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
9
|
+
this_dir = File.dirname(File.expand_path(file))
|
10
|
+
$: << this_dir
|
11
|
+
$: << this_dir + '/active_rdf/'
|
12
|
+
|
13
|
+
require 'active_rdf_helpers'
|
14
|
+
require 'active_rdf_log'
|
15
|
+
|
16
|
+
$log.info "ActiveRDF started, logging level: #{$log.level}"
|
17
|
+
|
18
|
+
# load standard classes that need to be loaded at startup
|
19
|
+
require 'objectmanager/resource'
|
20
|
+
require 'objectmanager/namespace'
|
21
|
+
require 'federation/connection_pool'
|
22
|
+
require 'queryengine/query'
|
23
|
+
require 'federation/active_rdf_adapter'
|
24
|
+
|
25
|
+
def load_adapter s
|
26
|
+
begin
|
27
|
+
require s
|
28
|
+
rescue StandardError => e
|
29
|
+
$log.info "could not load adapter #{s}: #{e}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'rubygems'
|
34
|
+
#determine if we are installed as a gem right now:
|
35
|
+
if Gem::cache().search("activerdf").empty?
|
36
|
+
#we are not running as a gem
|
37
|
+
$log.info 'ActiveRDF is NOT installed as a Gem'
|
38
|
+
load_adapter this_dir + '/../activerdf-rdflite/lib/activerdf_rdflite/rdflite'
|
39
|
+
load_adapter this_dir + '/../activerdf-redland/lib/activerdf_redland/redland'
|
40
|
+
load_adapter this_dir + '/../activerdf-sparql/lib/activerdf_sparql/sparql'
|
41
|
+
load_adapter this_dir + '/../activerdf-yars/lib/activerdf_yars/jars2'
|
42
|
+
else
|
43
|
+
#we are indeed running as a gem
|
44
|
+
require 'gem_plugin'
|
45
|
+
$log.info 'ActiveRDF is installed as a Gem'
|
46
|
+
GemPlugin::Manager.instance.load "activerdf" => GemPlugin::INCLUDE
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# defining ActiveRDF errors
|
2
|
+
class ActiveRdfError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
# adding bool_accessor to ruby
|
6
|
+
class Module
|
7
|
+
def bool_accessor *syms
|
8
|
+
attr_accessor(*syms)
|
9
|
+
syms.each { |sym| alias_method "#{sym}?", sym }
|
10
|
+
remove_method(*syms)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
$log =
|
4
|
+
begin
|
5
|
+
# us the rails logger if running under rails
|
6
|
+
RAILS_DEFAULT_LOGGER
|
7
|
+
rescue NameError
|
8
|
+
unless ENV['ACTIVE_RDF_LOG'].nil?
|
9
|
+
# write to environment variable $RDF_LOG if set
|
10
|
+
Logger.new(ENV['ACTIVE_RDF_LOG'], 1, 100*1024)
|
11
|
+
else
|
12
|
+
require 'tmpdir'
|
13
|
+
# else just write to the temp dir
|
14
|
+
Logger.new(Dir.tmpdir.to_s + "/activerdf.log", 1, 100*1024);
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# if user has specified loglevel we use that, otherwise we use default level
|
19
|
+
# in the environment variable ACTIVE_RDF_LOG_LEVEL we expect numbers, which we
|
20
|
+
# have to convert
|
21
|
+
if ENV['ACTIVE_RDF_LOG_LEVEL'].nil?
|
22
|
+
$log.level = Logger::WARN
|
23
|
+
else
|
24
|
+
$log.level = ENV['ACTIVE_RDF_LOG_LEVEL'].to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
class Logger
|
28
|
+
def debug_pp(message, variable)
|
29
|
+
if variable.respond_to?(:join)
|
30
|
+
if variable.empty?
|
31
|
+
debug(sprintf(message, "empty"))
|
32
|
+
else
|
33
|
+
debug(sprintf(message, variable.join(', ')))
|
34
|
+
end
|
35
|
+
else
|
36
|
+
if variable.nil?
|
37
|
+
debug(sprintf(message, 'empty'))
|
38
|
+
else
|
39
|
+
debug(sprintf(message, variable))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|