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