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,114 @@
|
|
1
|
+
# Maintains pool of adapter instances that are connected to datasources
|
2
|
+
# returns right adapter for a given datasource, by either reusing
|
3
|
+
# existing adapter-instance or creating new adapter-instance
|
4
|
+
#
|
5
|
+
# Author:: Eyal Oren
|
6
|
+
# Copyright:: (c) 2005-2006
|
7
|
+
# License:: LGPL
|
8
|
+
|
9
|
+
require 'active_rdf'
|
10
|
+
|
11
|
+
class ConnectionPool
|
12
|
+
class << self
|
13
|
+
attr_accessor :write_adapter
|
14
|
+
|
15
|
+
# sets automatic flushing of data from adapters to original datasources
|
16
|
+
# (e.g. redland on-file database). If disabled, changes to an adapter are
|
17
|
+
# not written back into the original source: you need to invoke
|
18
|
+
# ConnectionPool.flush manually
|
19
|
+
bool_accessor :auto_flush
|
20
|
+
end
|
21
|
+
# pool of all adapters
|
22
|
+
@@adapter_pool = Array.new
|
23
|
+
|
24
|
+
# pool of connection parameters to all adapter
|
25
|
+
@@adapter_parameters = Array.new
|
26
|
+
|
27
|
+
# currently active write-adapter (we can only write to one at a time)
|
28
|
+
self.write_adapter = nil
|
29
|
+
|
30
|
+
# default setting for auto_flush
|
31
|
+
self.auto_flush = true
|
32
|
+
|
33
|
+
# adapters-classes known to the pool, registered by the adapter-class
|
34
|
+
# itself using register_adapter method, used to select new
|
35
|
+
# adapter-instance for requested connection type
|
36
|
+
@@registered_adapter_types = Hash.new
|
37
|
+
|
38
|
+
# clears the pool: removes all registered data sources
|
39
|
+
def ConnectionPool.clear
|
40
|
+
$log.info "ConnectionPool: clear called"
|
41
|
+
@@adapter_pool = []
|
42
|
+
@@adapter_parameters = []
|
43
|
+
self.write_adapter = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# flushes all openstanding changes into the original datasource.
|
47
|
+
def ConnectionPool.flush
|
48
|
+
write_adapter.flush
|
49
|
+
end
|
50
|
+
|
51
|
+
def ConnectionPool.adapter_types
|
52
|
+
@@registered_adapter_types.keys
|
53
|
+
end
|
54
|
+
|
55
|
+
# returns the set of currently registered read-access datasources
|
56
|
+
def ConnectionPool.read_adapters
|
57
|
+
@@adapter_pool.select {|adapter| adapter.reads? }
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns adapter-instance for given parameters (either existing or new)
|
61
|
+
def ConnectionPool.add_data_source(connection_params)
|
62
|
+
$log.info "ConnectionPool: add_data_source with params: #{connection_params.inspect}"
|
63
|
+
|
64
|
+
# either get the adapter-instance from the pool
|
65
|
+
# or create new one (and add it to the pool)
|
66
|
+
index = @@adapter_parameters.index(connection_params)
|
67
|
+
if index.nil?
|
68
|
+
# adapter not in the pool yet: create it,
|
69
|
+
# register its connection parameters in parameters-array
|
70
|
+
# and add it to the pool (at same index-position as parameters)
|
71
|
+
$log.debug("Create a new adapter for parameters #{connection_params.inspect}")
|
72
|
+
adapter = create_adapter(connection_params)
|
73
|
+
@@adapter_parameters << connection_params
|
74
|
+
@@adapter_pool << adapter
|
75
|
+
else
|
76
|
+
# if adapter parametrs registered already,
|
77
|
+
# then adapter must be in the pool, at the same index-position as its parameters
|
78
|
+
$log.debug("Reusing existing adapter")
|
79
|
+
adapter = @@adapter_pool[index]
|
80
|
+
end
|
81
|
+
|
82
|
+
# sets the adapter as current write-source if it can write
|
83
|
+
self.write_adapter = adapter if adapter.writes?
|
84
|
+
|
85
|
+
return adapter
|
86
|
+
end
|
87
|
+
|
88
|
+
# aliasing add_data_source as add
|
89
|
+
# (code bit more complicad since they are class methods)
|
90
|
+
class << self
|
91
|
+
alias add add_data_source
|
92
|
+
end
|
93
|
+
|
94
|
+
# adapter-types can register themselves with connection pool by
|
95
|
+
# indicating which adapter-type they are
|
96
|
+
def ConnectionPool.register_adapter(type, klass)
|
97
|
+
$log.info "ConnectionPool: registering adapter of type #{type} for class #{klass}"
|
98
|
+
@@registered_adapter_types[type] = klass
|
99
|
+
end
|
100
|
+
|
101
|
+
# create new adapter from connection parameters
|
102
|
+
def ConnectionPool.create_adapter connection_params
|
103
|
+
# lookup registered adapter klass
|
104
|
+
klass = @@registered_adapter_types[connection_params[:type]]
|
105
|
+
|
106
|
+
# raise error if adapter type unknown
|
107
|
+
raise(ActiveRdfError, "unknown adapter type #{connection_params[:type]}") if klass.nil?
|
108
|
+
|
109
|
+
# create new adapter-instance
|
110
|
+
klass.send(:new,connection_params)
|
111
|
+
end
|
112
|
+
|
113
|
+
private_class_method :create_adapter
|
114
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Manages the federation of datasources
|
2
|
+
# distributes queries to right datasources and merges their results
|
3
|
+
#
|
4
|
+
# Author:: Eyal Oren
|
5
|
+
# Copyright:: (c) 2005-2006
|
6
|
+
# License:: LGPL
|
7
|
+
require 'federation/connection_pool'
|
8
|
+
|
9
|
+
class FederationManager
|
10
|
+
# add triple s,p,o to the currently selected write-adapter
|
11
|
+
def FederationManager.add(s,p,o)
|
12
|
+
# TODO: allow addition of full graphs
|
13
|
+
$log.debug "FederationManager: add: triple is #{s} #{p} #{o}"
|
14
|
+
ConnectionPool.write_adapter.add(s,p,o)
|
15
|
+
end
|
16
|
+
|
17
|
+
# executes read-only queries
|
18
|
+
# by distributing query over complete read-pool
|
19
|
+
# and aggregating the results
|
20
|
+
def FederationManager.query(q, options={:flatten => true})
|
21
|
+
if ConnectionPool.read_adapters.empty?
|
22
|
+
raise ActiveRdfError, "cannot execute query without data sources"
|
23
|
+
end
|
24
|
+
|
25
|
+
$log.debug "FederationManager: query called with: #{q}"
|
26
|
+
# ask each adapter for query results
|
27
|
+
# and yield them consequtively
|
28
|
+
if block_given?
|
29
|
+
ConnectionPool.read_adapters.each do |source|
|
30
|
+
source.query(q) do |*clauses|
|
31
|
+
yield(*clauses)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# build Array of results from all sources
|
36
|
+
# TODO: write test for sebastian's select problem
|
37
|
+
# (without distinct, should get duplicates, they
|
38
|
+
# were filtered out when doing results.union)
|
39
|
+
results = []
|
40
|
+
ConnectionPool.read_adapters.each do |source|
|
41
|
+
source_results = source.query(q)
|
42
|
+
source_results.each do |clauses|
|
43
|
+
results << clauses
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# filter the empty results
|
48
|
+
results.reject {|ary| ary.empty? }
|
49
|
+
|
50
|
+
# remove duplicate results from multiple
|
51
|
+
# adapters if asked for distinct query
|
52
|
+
# (adapters return only distinct results,
|
53
|
+
# but they cannot check duplicates against each other)
|
54
|
+
results.uniq! if q.distinct?
|
55
|
+
|
56
|
+
# flatten results array if only one select clause
|
57
|
+
# to prevent unnecessarily nested array [[eyal],[renaud],...]
|
58
|
+
results.flatten! if q.select_clauses.size == 1 or q.ask?
|
59
|
+
|
60
|
+
# and remove array (return single value) unless asked not to
|
61
|
+
if options[:flatten]
|
62
|
+
case results.size
|
63
|
+
when 0
|
64
|
+
final_results = nil
|
65
|
+
when 1
|
66
|
+
final_results = results.first
|
67
|
+
else
|
68
|
+
final_results = results
|
69
|
+
end
|
70
|
+
else
|
71
|
+
final_results = results
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
$log.debug_pp("FederationManager: query results are %s", final_results)
|
76
|
+
|
77
|
+
|
78
|
+
return final_results
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Manages namespace abbreviations and expansions
|
2
|
+
#
|
3
|
+
# Author:: Eyal Oren
|
4
|
+
# Copyright:: (c) 2005-2006
|
5
|
+
# License:: LGPL
|
6
|
+
|
7
|
+
require 'active_rdf'
|
8
|
+
|
9
|
+
class Namespace
|
10
|
+
|
11
|
+
@@namespaces = Hash.new
|
12
|
+
@@inverted_namespaces = Hash.new
|
13
|
+
|
14
|
+
# registers a namespace prefix and its associated expansion (full URI)
|
15
|
+
# e.g. :rdf and 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
16
|
+
def self.register(prefix, fullURI)
|
17
|
+
raise ActiveRdfError, 'prefix nor uri can be empty' if (prefix.to_s.empty? or fullURI.to_s.empty?)
|
18
|
+
$log.info "Namespace: registering #{fullURI} to #{prefix}"
|
19
|
+
@@namespaces[prefix.to_sym] = fullURI.to_s
|
20
|
+
@@inverted_namespaces[fullURI.to_s] = prefix.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns a resource whose URI is formed by concatenation of prefix and localname
|
24
|
+
def self.lookup(prefix, localname)
|
25
|
+
full_resource = expand(prefix, localname)
|
26
|
+
$log.debug "Namespace: lookup for Resource #{full_resource} initiated"
|
27
|
+
RDFS::Resource.new(expand(prefix, localname))
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns URI (string) formed by concatenation of prefix and localname
|
31
|
+
def self.expand(prefix, localname)
|
32
|
+
@@namespaces[prefix.to_sym].to_s + localname.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns prefix (if known) for the non-local part of the URI,
|
36
|
+
# or nil if prefix not registered
|
37
|
+
def self.prefix(resource)
|
38
|
+
# get string representation of resource uri
|
39
|
+
uri = case resource
|
40
|
+
when RDFS::Resource: resource.uri
|
41
|
+
else resource.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# uri.to_s gives us the uri of the resource (if resource given)
|
45
|
+
# then we find the last occurrence of # or / (heuristical namespace
|
46
|
+
# delimitor)
|
47
|
+
delimiter = uri.rindex(/#|\//)
|
48
|
+
|
49
|
+
# if delimiter not found, URI cannot be split into (non)local-part
|
50
|
+
return uri if delimiter.nil?
|
51
|
+
|
52
|
+
# extract non-local part (including delimiter)
|
53
|
+
nonlocal = uri[0..delimiter]
|
54
|
+
|
55
|
+
@@inverted_namespaces[nonlocal]
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns local-part of URI
|
59
|
+
def self.localname(resource)
|
60
|
+
# get string representation of resource uri
|
61
|
+
uri = case resource
|
62
|
+
when RDFS::Resource: resource.uri
|
63
|
+
else resource.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
delimiter = uri.rindex(/#|\//)
|
67
|
+
if delimiter.nil?
|
68
|
+
uri
|
69
|
+
else
|
70
|
+
uri[delimiter+1..-1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Namespace.register(:rdf, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
|
76
|
+
Namespace.register(:rdfs, 'http://www.w3.org/2000/01/rdf-schema#')
|
77
|
+
Namespace.register(:owl, 'http://www.w3.org/2002/07/owl#')
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Constructs Ruby classes for RDFS classes (in the right namespace)
|
2
|
+
#
|
3
|
+
# Author:: Eyal Oren
|
4
|
+
# Copyright:: (c) 2005-2006
|
5
|
+
# License:: LGPL
|
6
|
+
require 'active_rdf'
|
7
|
+
|
8
|
+
class ObjectManager
|
9
|
+
# constructs empty Ruby classes for all RDF types found in the data
|
10
|
+
#
|
11
|
+
# allows users to invoke methods on classes (e.g. FOAF::Person) without
|
12
|
+
# getting symbol undefined errors (because e.g. foaf:person wasnt encountered
|
13
|
+
# before so no class was created for it)
|
14
|
+
def self.construct_classes
|
15
|
+
# find all rdf:types and construct class for each of them
|
16
|
+
#q = Query.new.select(:t).where(:s,Namespace.lookup(:rdf,:type),:t)
|
17
|
+
|
18
|
+
# find everything defined as rdfs:class or owl:class
|
19
|
+
type = Namespace.lookup(:rdf,:type)
|
20
|
+
rdfsklass = Namespace.lookup(:rdfs,:Class)
|
21
|
+
|
22
|
+
# TODO: we should not do this, we should not support OWL
|
23
|
+
# instead, owl:Class is defined as subclass-of rdfs:Class, so if the
|
24
|
+
# reasoner has access to owl definition it should work out fine.
|
25
|
+
owlklass = Namespace.lookup(:owl,:Class)
|
26
|
+
|
27
|
+
klasses = []
|
28
|
+
klasses << Query.new.distinct(:s).where(:s,type,rdfsklass).execute
|
29
|
+
klasses << Query.new.distinct(:s).where(:s,type,owlklass).execute
|
30
|
+
|
31
|
+
# flattening to get rid of nested arrays
|
32
|
+
# compacting array to get rid of nil (if one of these queries returned nil)
|
33
|
+
klasses = klasses.flatten.compact
|
34
|
+
$log.debug "ObjectManager: construct_classes: classes found: #{klasses}"
|
35
|
+
|
36
|
+
# then we construct a Ruby class for each found rdfs:class
|
37
|
+
# and return the set of all constructed classes
|
38
|
+
klasses.collect { |t| construct_class(t) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# constructs Ruby class for the given resource (and puts it into the module as
|
42
|
+
# defined by the registered namespace abbreviations)
|
43
|
+
def self.construct_class(resource)
|
44
|
+
# get prefix abbreviation and localname from type
|
45
|
+
# e.g. :foaf and Person
|
46
|
+
localname = Namespace.localname(resource)
|
47
|
+
prefix = Namespace.prefix(resource)
|
48
|
+
|
49
|
+
# find (ruby-acceptable) names for the module and class
|
50
|
+
# e.g. FOAF and Person
|
51
|
+
if prefix.nil?
|
52
|
+
# if the prefix is unknown, we create our own from the full URI
|
53
|
+
modulename = create_module_name(resource)
|
54
|
+
$log.debug "ObjectManager: construct_class: constructing modulename #{modulename} from URI #{resource}"
|
55
|
+
else
|
56
|
+
# otherwise we convert the registered prefix into a module name
|
57
|
+
modulename = prefix_to_module(prefix)
|
58
|
+
$log.debug "ObjectManager: construct_class: constructing modulename #{modulename} from registered prefix #{prefix}"
|
59
|
+
end
|
60
|
+
klassname = localname_to_class(localname)
|
61
|
+
|
62
|
+
# look whether module defined
|
63
|
+
# else: create it
|
64
|
+
_module = if Object.const_defined?(modulename.to_sym)
|
65
|
+
$log.debug "ObjectManager: construct_class: module name #{modulename} previously defined"
|
66
|
+
Object.const_get(modulename.to_sym)
|
67
|
+
else
|
68
|
+
$log.debug "ObjectManager: construct_class: defining module name #{modulename} now"
|
69
|
+
Object.const_set(modulename, Module.new)
|
70
|
+
end
|
71
|
+
|
72
|
+
# look whether class defined in that module
|
73
|
+
if _module.const_defined?(klassname.to_sym)
|
74
|
+
$log.debug "ObjectManager: construct_class: given class #{klassname} defined in the module"
|
75
|
+
# if so, return the existing class
|
76
|
+
_module.const_get(klassname.to_sym)
|
77
|
+
else
|
78
|
+
$log.debug "ObjectManager: construct_class: creating given class #{klassname}"
|
79
|
+
# otherwise: create it, inside that module, as subclass of RDFS::Resource
|
80
|
+
# (using toplevel Class.new to prevent RDFS::Class.new from being called)
|
81
|
+
klass = _module.module_eval("#{klassname} = Object::Class.new(RDFS::Resource)")
|
82
|
+
klass.class_uri = RDFS::Resource.new(resource.uri)
|
83
|
+
klass
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.prefix_to_module(prefix)
|
88
|
+
# TODO: remove illegal characters
|
89
|
+
prefix.to_s.upcase
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.localname_to_class(localname)
|
93
|
+
# replace illegal characters inside the uri
|
94
|
+
# and capitalize the classname
|
95
|
+
replace_illegal_chars(localname).capitalize
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.create_module_name(resource)
|
99
|
+
# TODO: write unit test to verify replacement of all illegal characters
|
100
|
+
|
101
|
+
# extract non-local part (including delimiter)
|
102
|
+
uri = resource.uri
|
103
|
+
delimiter = uri.rindex(/#|\//)
|
104
|
+
nonlocal = uri[0..delimiter]
|
105
|
+
|
106
|
+
# remove illegal characters appearing at the end of the uri (e.g. trailing
|
107
|
+
# slash)
|
108
|
+
cleaned_non_local = nonlocal.gsub(/[^a-zA-Z0-9]+$/, '')
|
109
|
+
|
110
|
+
# replace illegal chars within the uri
|
111
|
+
replace_illegal_chars(cleaned_non_local).upcase
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.replace_illegal_chars(name)
|
115
|
+
name.gsub(/[^a-zA-Z0-9]+/, '_')
|
116
|
+
end
|
117
|
+
|
118
|
+
#declare the class level methods as private with these directives
|
119
|
+
private_class_method :prefix_to_module
|
120
|
+
private_class_method :localname_to_class
|
121
|
+
private_class_method :create_module_name
|
122
|
+
private_class_method :replace_illegal_chars
|
123
|
+
end
|