talia_core 0.4.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/README.rdoc +41 -0
- data/bin/talia +33 -0
- data/lib/JXslt/jxslt.rb +60 -0
- data/lib/acts_as_roled.rb +11 -0
- data/lib/core_ext/platform.rb +9 -0
- data/lib/core_ext/string.rb +6 -0
- data/lib/core_ext.rb +1 -0
- data/lib/custom_template.rb +4 -0
- data/lib/loader_helper.rb +62 -0
- data/lib/mysql.rb +1214 -0
- data/lib/progressbar.rb +236 -0
- data/lib/role.rb +12 -0
- data/lib/talia_cl/command_line.rb +39 -0
- data/lib/talia_cl/commands/standalone/cl_options.rb +9 -0
- data/lib/talia_cl/commands/standalone/standalone_generate.rb +75 -0
- data/lib/talia_cl/commands/standalone.rb +25 -0
- data/lib/talia_cl/commands/talia_console/cl_options.rb +55 -0
- data/lib/talia_cl/commands/talia_console/console_commands.rb +37 -0
- data/lib/talia_cl/commands/talia_console/talia_commands.rb +131 -0
- data/lib/talia_cl/commands/talia_console.rb +47 -0
- data/lib/talia_cl/core_commands.rb +11 -0
- data/lib/talia_cl.rb +47 -0
- data/lib/talia_core/active_source.rb +372 -0
- data/lib/talia_core/active_source_parts/class_methods.rb +378 -0
- data/lib/talia_core/active_source_parts/predicate_handler.rb +89 -0
- data/lib/talia_core/active_source_parts/rdf.rb +131 -0
- data/lib/talia_core/active_source_parts/sql_helper.rb +36 -0
- data/lib/talia_core/active_source_parts/xml/base_builder.rb +47 -0
- data/lib/talia_core/active_source_parts/xml/generic_reader.rb +363 -0
- data/lib/talia_core/active_source_parts/xml/rdf_builder.rb +88 -0
- data/lib/talia_core/active_source_parts/xml/source_builder.rb +73 -0
- data/lib/talia_core/active_source_parts/xml/source_reader.rb +20 -0
- data/lib/talia_core/agent.rb +14 -0
- data/lib/talia_core/background_jobs/job.rb +82 -0
- data/lib/talia_core/background_jobs/progress_job.rb +68 -0
- data/lib/talia_core/collection.rb +13 -0
- data/lib/talia_core/data_types/data_loader.rb +92 -0
- data/lib/talia_core/data_types/data_record.rb +105 -0
- data/lib/talia_core/data_types/delayed_copier.rb +76 -0
- data/lib/talia_core/data_types/file_record.rb +59 -0
- data/lib/talia_core/data_types/file_store.rb +306 -0
- data/lib/talia_core/data_types/iip_data.rb +153 -0
- data/lib/talia_core/data_types/iip_loader.rb +127 -0
- data/lib/talia_core/data_types/image_data.rb +32 -0
- data/lib/talia_core/data_types/media_link.rb +19 -0
- data/lib/talia_core/data_types/mime_mapping.rb +45 -0
- data/lib/talia_core/data_types/path_helpers.rb +77 -0
- data/lib/talia_core/data_types/pdf_data.rb +42 -0
- data/lib/talia_core/data_types/simple_text.rb +36 -0
- data/lib/talia_core/data_types/temp_file_handling.rb +85 -0
- data/lib/talia_core/data_types/xml_data.rb +169 -0
- data/lib/talia_core/dc_resource.rb +20 -0
- data/lib/talia_core/dummy_handler.rb +34 -0
- data/lib/talia_core/dummy_source.rb +20 -0
- data/lib/talia_core/errors.rb +25 -0
- data/lib/talia_core/initializer.rb +427 -0
- data/lib/talia_core/ordered_source.rb +228 -0
- data/lib/talia_core/rails_ext/actionpack/action_controller/record_identifier.rb +13 -0
- data/lib/talia_core/rails_ext/actionpack/action_controller.rb +1 -0
- data/lib/talia_core/rails_ext/actionpack.rb +1 -0
- data/lib/talia_core/rails_ext.rb +1 -0
- data/lib/talia_core/rdf_import.rb +90 -0
- data/lib/talia_core/rdf_resource.rb +159 -0
- data/lib/talia_core/semantic_collection_item.rb +93 -0
- data/lib/talia_core/semantic_collection_wrapper.rb +324 -0
- data/lib/talia_core/semantic_property.rb +7 -0
- data/lib/talia_core/semantic_relation.rb +67 -0
- data/lib/talia_core/source.rb +323 -0
- data/lib/talia_core/source_transfer_object.rb +38 -0
- data/lib/talia_core/workflow/base.rb +15 -0
- data/lib/talia_core/workflow/publication_workflow.rb +62 -0
- data/lib/talia_core/workflow.rb +300 -0
- data/lib/talia_core.rb +9 -0
- data/lib/talia_dependencies.rb +12 -0
- data/lib/talia_util/bar_progressor.rb +15 -0
- data/lib/talia_util/configuration/config_file.rb +48 -0
- data/lib/talia_util/configuration/database_config.rb +40 -0
- data/lib/talia_util/configuration/mysql_database_setup.rb +104 -0
- data/lib/talia_util/data_import.rb +91 -0
- data/lib/talia_util/image_conversions.rb +82 -0
- data/lib/talia_util/import_job_helper.rb +132 -0
- data/lib/talia_util/io_helper.rb +54 -0
- data/lib/talia_util/progressable.rb +38 -0
- data/lib/talia_util/progressbar.rb +236 -0
- data/lib/talia_util/rdf_update.rb +80 -0
- data/lib/talia_util/some_sigla.xml +1960 -0
- data/lib/talia_util/test_helpers.rb +151 -0
- data/lib/talia_util/util.rb +226 -0
- data/lib/talia_util/yaml_import.rb +80 -0
- data/lib/talia_util.rb +13 -0
- data/lib/user.rb +116 -0
- data/lib/version.rb +15 -0
- data/test/core_ext/string_test.rb +11 -0
- data/test/custom_template_test.rb +8 -0
- data/test/talia_core/active_source_predicate_test.rb +54 -0
- data/test/talia_core/active_source_rdf_test.rb +89 -0
- data/test/talia_core/active_source_test.rb +631 -0
- data/test/talia_core/data_types/data_loader_test.rb +123 -0
- data/test/talia_core/data_types/data_record_test.rb +40 -0
- data/test/talia_core/data_types/file_record_test.rb +171 -0
- data/test/talia_core/data_types/iip_data_test.rb +130 -0
- data/test/talia_core/data_types/image_data_test.rb +88 -0
- data/test/talia_core/data_types/pdf_data_test.rb +68 -0
- data/test/talia_core/data_types/xml_data_test.rb +134 -0
- data/test/talia_core/generic_xml_test.rb +83 -0
- data/test/talia_core/initializer_test.rb +36 -0
- data/test/talia_core/ordered_source_test.rb +398 -0
- data/test/talia_core/rdf_resource_test.rb +115 -0
- data/test/talia_core/semantic_collection_item_test.rb +129 -0
- data/test/talia_core/source_reader_test.rb +33 -0
- data/test/talia_core/source_test.rb +484 -0
- data/test/talia_core/source_transfer_object_test.rb +24 -0
- data/test/talia_core/workflow/publication_workflow_test.rb +242 -0
- data/test/talia_core/workflow/user_class_for_workflow.rb +35 -0
- data/test/talia_core/workflow/workflow_base_test.rb +21 -0
- data/test/talia_core/workflow_test.rb +19 -0
- data/test/talia_util/import_job_helper_test.rb +46 -0
- data/test/test_helper.rb +68 -0
- metadata +262 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module TaliaCore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Import RDF data directly into the triple store.
|
|
6
|
+
# This is called to import onotologies and other RDF data.
|
|
7
|
+
class RdfImport
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
# Import the given files.
|
|
12
|
+
# The rdf_syntax may be nil. If "auto" is given, it will use a default
|
|
13
|
+
# value for each imported file
|
|
14
|
+
def import(rdf_syntax, files, context=nil)
|
|
15
|
+
puts "Importing #{files.size} files into the triple store."
|
|
16
|
+
|
|
17
|
+
raise(ArgumentError, "Cannot use context, adapter doesn't support it.") if(context && !adapter.supports_context?)
|
|
18
|
+
|
|
19
|
+
# check if the connection to te triplestore is ok...otherwise exit the script
|
|
20
|
+
if !adapter
|
|
21
|
+
puts "\nERROR: impossible to open a connection to the triple store. Check your system and your configuration files!\n\n"
|
|
22
|
+
exit(1)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# try to load every file into the triple store
|
|
26
|
+
files.each do |file|
|
|
27
|
+
import_file(file, rdf_syntax, context)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
adapter.save if(adapter.respond_to?(:save))
|
|
31
|
+
|
|
32
|
+
puts "\n--> Importing rdf/rdfs file: complete!\n\n"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def import_file(file, syntax, context)
|
|
36
|
+
puts "\tLoading: " << file.to_s
|
|
37
|
+
|
|
38
|
+
my_context = make_ontology_context(context, file)
|
|
39
|
+
|
|
40
|
+
# load rdf/rdfs file into triplestore
|
|
41
|
+
begin
|
|
42
|
+
params = [ file ]
|
|
43
|
+
# Other than the adapter, we prefer rdfxml syntax
|
|
44
|
+
params << ((syntax && syntax != 'auto') ? syntax : 'rdfxml')
|
|
45
|
+
params << my_context if(my_context)
|
|
46
|
+
adapter.load(*params)
|
|
47
|
+
rescue Exception => e
|
|
48
|
+
puts "\tProblem loading #{file.to_s}: (#{e.message}) File not loaded!"
|
|
49
|
+
puts e.backtrace
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Clear the currently registered ontologies
|
|
54
|
+
def clear_file_contexts
|
|
55
|
+
# Remove all registered contexts
|
|
56
|
+
to_clear = Query.new(N::URI).select(:context).distinct.where(N::TALIA.rdf_context_space, N::TALIA.rdf_file_context, :context).execute
|
|
57
|
+
to_clear.each do |context|
|
|
58
|
+
adapter.clear(context)
|
|
59
|
+
end
|
|
60
|
+
FederationManager.delete(N::TALIA.rdf_context_space, N::TALIA.rdf_file_context, nil)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def adapter
|
|
66
|
+
@adapter ||= ConnectionPool.write_adapter
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Prepare the context for the ontology import. All contexts will be registered
|
|
70
|
+
# to the N::TALIA.rdf_context_space resource
|
|
71
|
+
def make_ontology_context(context, file)
|
|
72
|
+
return unless(context)
|
|
73
|
+
|
|
74
|
+
raise(ArgumentError, "Empty context") unless(context != '')
|
|
75
|
+
|
|
76
|
+
file_context = if(context.to_s == 'auto')
|
|
77
|
+
name = URI.encode(File.basename(file, File.extname(file))).gsub(/\./, '_')
|
|
78
|
+
N::URI.new(N::TALIA + name)
|
|
79
|
+
elsif(context)
|
|
80
|
+
N::URI.new(N::TALIA + context)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
FederationManager.add(N::TALIA.rdf_context_space, N::TALIA.rdf_file_context, file_context)
|
|
84
|
+
|
|
85
|
+
file_context
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module TaliaCore
|
|
2
|
+
|
|
3
|
+
# This class encapsulates all functionality to access a specific resource
|
|
4
|
+
# in the RDF store. It is analogous to the RDFS::Resource class in ActiveRDF.
|
|
5
|
+
#
|
|
6
|
+
# However, it's specifically tailored for the use with Talia, and avoids some
|
|
7
|
+
# of the pitfalls of the original class.
|
|
8
|
+
class RdfResource
|
|
9
|
+
|
|
10
|
+
include RDFS::ResourceLike
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
|
|
14
|
+
# A list of "default" types that will be added to all resources
|
|
15
|
+
def default_types
|
|
16
|
+
@default_types ||= [
|
|
17
|
+
N::SourceClass.new(N::RDFS.Resource)
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The class of objects that will be "produced" by this RdfResource
|
|
24
|
+
attr_writer :object_class
|
|
25
|
+
|
|
26
|
+
def object_class
|
|
27
|
+
@object_class ||= TaliaCore::Source
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Initialize a new resource with the given URI
|
|
31
|
+
def initialize(uri)
|
|
32
|
+
@uri = N::URI.new(uri)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Direct writing of a predicate, with having to fetch a list first
|
|
36
|
+
def direct_write_predicate(predicate, value)
|
|
37
|
+
FederationManager.add(self, predicate, value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Clears all rdf for this resource. FIXME: Not context-aware.
|
|
41
|
+
def clear_rdf
|
|
42
|
+
FederationManager.delete_all(self)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Removes the given predicate (restrict to the triple with the
|
|
46
|
+
# given value if a value is given).
|
|
47
|
+
def remove(predicate, value = nil)
|
|
48
|
+
FederationManager.delete(self, predicate, value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the value(s) of the given predicates as a PropertyList filled
|
|
52
|
+
# with the defined object_class objects.
|
|
53
|
+
def [](predicate)
|
|
54
|
+
predicate = N::URI.new(predicate) unless(predicate.kind_of?(N::URI))
|
|
55
|
+
|
|
56
|
+
property_list = Query.new(object_class).distinct(:o).where(self, predicate, :o).execute
|
|
57
|
+
|
|
58
|
+
PropertyList.new(predicate, property_list, self, source_exists?)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns an on-the-fly object that can be used to query for "inverse"
|
|
62
|
+
# properties of this resource (meaning triples that have the current
|
|
63
|
+
# resource as an object.)
|
|
64
|
+
#
|
|
65
|
+
# The returned object will respond to a [] (array accessor) call which
|
|
66
|
+
# allows the user to specify the predicate to use.
|
|
67
|
+
#
|
|
68
|
+
# Example: <tt>resource.inverse[N::DNCS::title]</tt>
|
|
69
|
+
#
|
|
70
|
+
# The [] method will return a list of objects that are instances of object_class
|
|
71
|
+
def inverse
|
|
72
|
+
inverseobj = Object.new
|
|
73
|
+
inverseobj.instance_variable_set(:@obj_uri, self)
|
|
74
|
+
inverseobj.instance_variable_set(:@obj_class, object_class)
|
|
75
|
+
|
|
76
|
+
class <<inverseobj
|
|
77
|
+
|
|
78
|
+
def [](property)
|
|
79
|
+
property = N::URI.new(property) unless(property.kind_of?(N::URI))
|
|
80
|
+
Query.new(@obj_class).distinct(:s).where(:s, property, @obj_uri).execute
|
|
81
|
+
end
|
|
82
|
+
private(:type)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return inverseobj
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Returns the uri of this resource as a string
|
|
90
|
+
def uri
|
|
91
|
+
@uri.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the predicates that are directly defined for this resource
|
|
95
|
+
def direct_predicates
|
|
96
|
+
Query.new(N::Predicate).distinct(:p).where(self, :p, :o).execute
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns the "inverse" predicates for the resource. these are the predicates
|
|
100
|
+
# for which this resource exists as an object
|
|
101
|
+
def inverse_predicates
|
|
102
|
+
qry = Query.new.distinct.select(:p)
|
|
103
|
+
qry.where(:s, :p, N::URI.new(uri.to_s))
|
|
104
|
+
qry.execute.collect{ |res| N::Predicate.new(res.uri) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Saves the current resource and it's properties to the RDF. (This has
|
|
108
|
+
# been optimized so that if only one RDF backend is present it won't do
|
|
109
|
+
# any copying around.
|
|
110
|
+
def save
|
|
111
|
+
if((ConnectionPool.read_adapters.size == 1) &&
|
|
112
|
+
(ConnectionPool.write_adapter == ConnectionPool.read_adapters.first))
|
|
113
|
+
save_default_types # Only write the "default" types to the store
|
|
114
|
+
else
|
|
115
|
+
full_save # Do the full save operation
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the types of this resource as N::SourceClass objects
|
|
120
|
+
def types
|
|
121
|
+
types = Query.new(N::SourceClass).distinct(:t).where(self,N::RDF::type,:t).execute
|
|
122
|
+
# Add the "default" types if necessary
|
|
123
|
+
self.class.default_types.each do |def_type|
|
|
124
|
+
types << def_type unless(types.include?(def_type))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Make a property list for the types.
|
|
128
|
+
PropertyList.new(N::RDF::type, types, self, source_exists?)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# Saves the the "default" types of this resource to the writing adapter
|
|
134
|
+
def save_default_types
|
|
135
|
+
self.class.default_types.each do |t|
|
|
136
|
+
FederationManager.add(self, N::RDF::type, t)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# "Full" save which reads the triples from all adapters, and saves them
|
|
141
|
+
# the writing adapter. This operation can be very slow.
|
|
142
|
+
def full_save
|
|
143
|
+
types.each do |t|
|
|
144
|
+
FederationManager.add(self, N::RDF::type, t)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Query.new(N::URI).distinct(:p,:o).where(self, :p, :o).execute do |p, o|
|
|
148
|
+
FederationManager.add(self, p, o)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns true if the corresponding Source already exists in the database
|
|
153
|
+
def source_exists?
|
|
154
|
+
# FIXME: Just a helper as long as ActiveSource is not completely integrated!
|
|
155
|
+
Source.exists?(@uri) || ActiveSource.exists?(:uri => @uri.to_s)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module TaliaCore
|
|
2
|
+
|
|
3
|
+
# This is a single item in a semantic collection wrapper. The contents are
|
|
4
|
+
# * fat_relation - a SemanticRelation with all the columns needed to build the
|
|
5
|
+
# related objects
|
|
6
|
+
# * plain_relation - a normal semantic relation object (either this or fat_relation)
|
|
7
|
+
# should be given
|
|
8
|
+
# Only one of the above should be usually given
|
|
9
|
+
class SemanticCollectionItem
|
|
10
|
+
|
|
11
|
+
attr_reader :plain_relation, :fat_relation
|
|
12
|
+
|
|
13
|
+
def initialize(relation, plain_or_fat)
|
|
14
|
+
case plain_or_fat
|
|
15
|
+
when :plain
|
|
16
|
+
@plain_relation = relation
|
|
17
|
+
when :fat
|
|
18
|
+
@fat_relation = relation
|
|
19
|
+
else
|
|
20
|
+
raise(ArgumentError, "Unknown type")
|
|
21
|
+
end
|
|
22
|
+
@object_type = SemanticCollectionWrapper.special_types[relation.predicate_uri.to_s]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Return the relation object that was given
|
|
26
|
+
def relation
|
|
27
|
+
@fat_relation || @plain_relation
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Return the "value" (Semantic relation value or the related ActiveSource)
|
|
31
|
+
def value
|
|
32
|
+
semprop = object.is_a?(SemanticProperty)
|
|
33
|
+
if(@object_type)
|
|
34
|
+
assit(object, "Must have object for #{relation.predicate_uri}")
|
|
35
|
+
raise(ArgumentError, 'Must not have a property for a typed item') if(semprop)
|
|
36
|
+
@object_type.new(object.uri.to_s)
|
|
37
|
+
else
|
|
38
|
+
# Plain, return the object or the value for SemanticProperties
|
|
39
|
+
semprop ? object.value : object
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Creates an object from the given relation
|
|
44
|
+
def object
|
|
45
|
+
@object ||= begin
|
|
46
|
+
if(@fat_relation)
|
|
47
|
+
create_object_from(@fat_relation)
|
|
48
|
+
elsif(@plain_relation)
|
|
49
|
+
@plain_relation.object
|
|
50
|
+
else
|
|
51
|
+
raise(ArgumentError, "No relation was given to this object")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ==(compare)
|
|
57
|
+
self.value == compare
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# if the object is a relation, it will r
|
|
61
|
+
|
|
62
|
+
# Creates an object frm the given "fat" relation. This retrieves the data
|
|
63
|
+
# from the relation object and instantiates it just like it would be after
|
|
64
|
+
# a find operation.
|
|
65
|
+
def create_object_from(fat_relation)
|
|
66
|
+
# First we find out which class (table our target object is in)
|
|
67
|
+
klass = fat_relation.object_type.constantize
|
|
68
|
+
record = nil
|
|
69
|
+
if(klass == TaliaCore::ActiveSource)
|
|
70
|
+
# We prepare a hash of properties for the ActiveSource
|
|
71
|
+
record = {
|
|
72
|
+
'uri' => fat_relation.object_uri,
|
|
73
|
+
'created_at' => fat_relation.object_created_at,
|
|
74
|
+
'updated_at' => fat_relation.object_updated_at,
|
|
75
|
+
'type' => fat_relation.object_realtype
|
|
76
|
+
}
|
|
77
|
+
elsif(klass == TaliaCore::SemanticProperty)
|
|
78
|
+
# We prepare a hash of properties for the SemanticProperty
|
|
79
|
+
record = {
|
|
80
|
+
'value' => fat_relation.property_value,
|
|
81
|
+
'created_at' => fat_relation.property_created_at,
|
|
82
|
+
'updated_at' => fat_relation.property_updated_at
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
# Common attributes
|
|
86
|
+
record['id'] = fat_relation.object_id
|
|
87
|
+
# Instantiate the new record
|
|
88
|
+
klass.send(:instantiate, record)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
module TaliaCore
|
|
2
|
+
|
|
3
|
+
# Wraps the Array/Collection returned from the ActiveRecord, simply
|
|
4
|
+
# "hiding" the SemanticProperty objects behind strings.
|
|
5
|
+
class SemanticCollectionWrapper
|
|
6
|
+
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :force_type
|
|
10
|
+
|
|
11
|
+
# Simple hash that checks if a type if property requires "special" handling
|
|
12
|
+
# This will cause the wrapper to accept ActiveSource relations and all
|
|
13
|
+
# sources will be casted to the given type
|
|
14
|
+
def self.special_types
|
|
15
|
+
@special_types ||= {
|
|
16
|
+
N::RDF.type.to_s => N::SourceClass
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Initialize the list
|
|
21
|
+
def initialize(source, predicate)
|
|
22
|
+
# raise(ActiveRecord::RecordNotSaved, "No properties on unsaved record.") if(source.new_record?)
|
|
23
|
+
@assoc_source = source
|
|
24
|
+
@assoc_predicate = if(predicate.respond_to?(:uri))
|
|
25
|
+
predicate.uri.to_s
|
|
26
|
+
else
|
|
27
|
+
predicate.to_s
|
|
28
|
+
end
|
|
29
|
+
@force_type = self.class.special_types[@assoc_predicate]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get the element '''value''' at the given index
|
|
33
|
+
def at(index)
|
|
34
|
+
items.at(index).value if(items.at(index))
|
|
35
|
+
end
|
|
36
|
+
alias :[] :at
|
|
37
|
+
|
|
38
|
+
def first
|
|
39
|
+
item = items.first
|
|
40
|
+
item ? item.value : nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def last
|
|
44
|
+
item = items.last
|
|
45
|
+
item ? item.value : nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Gets the value at the given index
|
|
49
|
+
def get_item_at(index)
|
|
50
|
+
items.at(index).object if(items.at(index))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Iterates over each '''value''' of the items in the relation.
|
|
54
|
+
def each
|
|
55
|
+
items.each { |item| yield(item.value) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Collect method for the semantic wrapper
|
|
59
|
+
def collect
|
|
60
|
+
items.collect { |item| yield(item.value) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Iterates of each '''target''' of the items in the relation. (This
|
|
64
|
+
# will pass in SemanticProperty objects instead of the value
|
|
65
|
+
def each_item
|
|
66
|
+
items.each { |item| yield(item.object) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns an array with all values in the collection
|
|
70
|
+
def values
|
|
71
|
+
items.collect { |item| item.value }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Size of the collection.
|
|
75
|
+
def size
|
|
76
|
+
return items.size if(loaded?)
|
|
77
|
+
if(@items)
|
|
78
|
+
# This is not really possible without loading, so we do it
|
|
79
|
+
load!
|
|
80
|
+
items.size
|
|
81
|
+
else
|
|
82
|
+
SemanticRelation.count(:conditions => {
|
|
83
|
+
'subject_id' => @assoc_source.id,
|
|
84
|
+
'predicate_uri' => @assoc_predicate })
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Joins the elments into a string
|
|
89
|
+
def join(join_str = ', ')
|
|
90
|
+
strs = items.collect { |item| item.value.to_s }
|
|
91
|
+
strs.join(join_str)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Index of the given value
|
|
95
|
+
def index(value)
|
|
96
|
+
items.index(value)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Check if the collection includes the value
|
|
100
|
+
def include?(value)
|
|
101
|
+
items.include?(value)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Get the index of the given item
|
|
105
|
+
|
|
106
|
+
# Push to collection. Giving a string will create a property to be created,
|
|
107
|
+
# saved and associated.
|
|
108
|
+
def <<(value)
|
|
109
|
+
add_with_order(value, nil)
|
|
110
|
+
end
|
|
111
|
+
alias_method :concat, '<<'
|
|
112
|
+
|
|
113
|
+
# Adds the object and gives the relation the given order.
|
|
114
|
+
def add_with_order(value, order)
|
|
115
|
+
# We use order exclusively for "ordering" predicates
|
|
116
|
+
assit_equal(TaliaCore::OrderedSource.index_to_predicate(order), @assoc_predicate) if(order)
|
|
117
|
+
raise(ArgumentError, "cannot add nil") unless(value != nil)
|
|
118
|
+
if(value.kind_of?(Array))
|
|
119
|
+
value.each { |v| add_record_for(v, order) }
|
|
120
|
+
else
|
|
121
|
+
add_record_for(value, order)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Replace a value with a new one
|
|
126
|
+
def replace(old_value, new_value)
|
|
127
|
+
idx = items.index(old_value)
|
|
128
|
+
items[idx].destroy
|
|
129
|
+
add_record_for(new_value) { |new_item| items[idx] = new_item }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Remove the given value. With no parameters, the whole list will be
|
|
133
|
+
# cleared and the RDF will be updated immediately.
|
|
134
|
+
def remove(*params)
|
|
135
|
+
if(params.length > 0)
|
|
136
|
+
params.each { |par| remove_relation(par) }
|
|
137
|
+
else
|
|
138
|
+
if(loaded?)
|
|
139
|
+
items.each { |item| item.relation.destroy }
|
|
140
|
+
else
|
|
141
|
+
SemanticRelation.destroy_all(
|
|
142
|
+
:subject_id => @assoc_source.id,
|
|
143
|
+
:predicate_uri => @assoc_predicate
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
@assoc_source.my_rdf.remove(N::URI.new(@assoc_predicate))
|
|
147
|
+
@items = []
|
|
148
|
+
@loaded = true
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# This attempts to save the items to the database
|
|
153
|
+
def save_items!
|
|
154
|
+
return if(clean?) # If there are no items, nothing was modified
|
|
155
|
+
@assoc_source.save! unless(@assoc_source.id)
|
|
156
|
+
@items.each do |item|
|
|
157
|
+
next if(item.fat_relation) # we skip the fat relations, they are never new and never saveable
|
|
158
|
+
rel = item.plain_relation
|
|
159
|
+
must_save = rel.new_record?
|
|
160
|
+
if(rel.object_id.nil?)
|
|
161
|
+
rel.object.save! if(rel.object.new_record?)
|
|
162
|
+
rel.object_id = rel.object.id
|
|
163
|
+
must_save = true
|
|
164
|
+
end
|
|
165
|
+
unless(rel.subject_id != nil)
|
|
166
|
+
rel.subject_id = @assoc_source.id
|
|
167
|
+
must_save = true
|
|
168
|
+
end
|
|
169
|
+
rel.save! if(must_save)
|
|
170
|
+
end
|
|
171
|
+
@items = nil unless(loaded?) # Otherwise we'll have trouble reload-and merging
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Indicates of the internal collection is loaded
|
|
175
|
+
def loaded?
|
|
176
|
+
@loaded
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Indicates that the wraper is "clean", that is it hasn't been written to
|
|
180
|
+
# or read from
|
|
181
|
+
def clean?
|
|
182
|
+
@items.nil?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def empty?
|
|
186
|
+
self.size == 0
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Injector for a fat relation. This must take place before flagging the
|
|
190
|
+
# source as "loaded"
|
|
191
|
+
def inject_fat_item(fat_rel)
|
|
192
|
+
raise(RuntimeError, 'Trying to inject in loaded object.') if(loaded?)
|
|
193
|
+
@items ||= []
|
|
194
|
+
@items << SemanticCollectionItem.new(fat_rel, :fat)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
# Load the current relation. (Loading should be lazy, so that the database
|
|
200
|
+
# is not hit until needed.
|
|
201
|
+
def load!
|
|
202
|
+
# The "fat" relations contain all the data to build the related objects if
|
|
203
|
+
# required
|
|
204
|
+
relations = SemanticRelation.find_fat_relations(@assoc_source, @assoc_predicate)
|
|
205
|
+
|
|
206
|
+
init_from_fat_rels(relations)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Inject a fat relation into the items
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Inititlizes the collection from the given collection of "fat" relations
|
|
213
|
+
def init_from_fat_rels(fat_relations)
|
|
214
|
+
# Check if there are records that have been added previously
|
|
215
|
+
old_items = @items
|
|
216
|
+
# Create the internal collection
|
|
217
|
+
@items = Array.new(fat_relations.size)
|
|
218
|
+
fat_relations.each_index do |idx|
|
|
219
|
+
rel = SemanticCollectionItem.new(fat_relations.at(idx), :fat)
|
|
220
|
+
@items[idx] = rel
|
|
221
|
+
end
|
|
222
|
+
@items = (@items | old_items) if(old_items)
|
|
223
|
+
@loaded = true
|
|
224
|
+
@items
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Returns the items in the collection
|
|
228
|
+
def items
|
|
229
|
+
load! unless(loaded?)
|
|
230
|
+
@items
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Deletes the relation where with the current predicate and the given
|
|
234
|
+
# value.
|
|
235
|
+
def remove_relation(value)
|
|
236
|
+
idx = items.index(value)
|
|
237
|
+
return unless(idx)
|
|
238
|
+
remove_at(idx)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Removes a relation at the given index
|
|
242
|
+
def remove_at(index)
|
|
243
|
+
items.at(index).relation.destroy
|
|
244
|
+
items.delete_at(index)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Creates a record for a value and adds it. This will add the given value if it's
|
|
248
|
+
# a database record and otherwise create a property with the given value.
|
|
249
|
+
# The block can be given when you want to add the new SemanticCollectionItem
|
|
250
|
+
# to the colleciton in a specific way.
|
|
251
|
+
# are loaded.
|
|
252
|
+
def add_record_for(value, order = nil)
|
|
253
|
+
if(@force_type)
|
|
254
|
+
# If we have a type, we must transform the value
|
|
255
|
+
value = value.respond_to?(:uri) ? value.uri : value
|
|
256
|
+
value = ActiveSource.new(value.to_s)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
value = check_for_source(value) if(value.is_a?(ActiveSource))
|
|
260
|
+
|
|
261
|
+
rel = create_predicate(value)
|
|
262
|
+
rel.rel_order = order if(order)
|
|
263
|
+
item = SemanticCollectionItem.new(rel, :plain)
|
|
264
|
+
block_given? ? yield(item) : insert_item(item)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Insert a new item
|
|
268
|
+
def insert_item(item)
|
|
269
|
+
@items ||= []
|
|
270
|
+
@items << item
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Write a triple to the store. For normal operation it's recommended that
|
|
274
|
+
# the usual accessor methods are used. This method does less checking
|
|
275
|
+
# and does not accept array objects as value.
|
|
276
|
+
def create_predicate(value)
|
|
277
|
+
# TODO: Semantic Properties should only be created inside, since assigning
|
|
278
|
+
# one to multiple relations and then deleting breaks integrity.
|
|
279
|
+
# The whole semantic property should be flattened into a field in
|
|
280
|
+
# SemanticRelation anyway.
|
|
281
|
+
assit(!value.is_a?(SemanticProperty), "Should not pass in Semantic Properties here!")
|
|
282
|
+
# We need to manually create the relation, to add the predicate_url
|
|
283
|
+
to_add = SemanticRelation.new(
|
|
284
|
+
:subject_id => @assoc_source.id,
|
|
285
|
+
:predicate_uri => @assoc_predicate
|
|
286
|
+
) # Create a new relation linked to this object
|
|
287
|
+
|
|
288
|
+
if(value.is_a?(TaliaCore::ActiveSource) || value.is_a?(TaliaCore::SemanticProperty))
|
|
289
|
+
to_add.object = value
|
|
290
|
+
else
|
|
291
|
+
prop = TaliaCore::SemanticProperty.new
|
|
292
|
+
prop.value = value
|
|
293
|
+
to_add.object = prop
|
|
294
|
+
end
|
|
295
|
+
to_add
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# This "checks" for the given source. If a source with the same URI has been
|
|
301
|
+
# added to any collection wrapper unsaved
|
|
302
|
+
def check_for_source(source)
|
|
303
|
+
return source unless(source.new_record?)
|
|
304
|
+
cached = unsaved_source_cache[source.uri.to_s]
|
|
305
|
+
if(cached.nil?)
|
|
306
|
+
unsaved_source_cache[source.uri.to_s] = source
|
|
307
|
+
cached = source
|
|
308
|
+
end
|
|
309
|
+
cached
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Cache for sources that were added as unsaved elements
|
|
313
|
+
def self.unsaved_source_cache
|
|
314
|
+
@unsaved_source_cache ||= {}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def unsaved_source_cache
|
|
318
|
+
SemanticCollectionWrapper.unsaved_source_cache
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
end
|