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,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
|