activerdf 1.0

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