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.
@@ -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