talia_core 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,378 @@
|
|
1
|
+
module TaliaCore
|
2
|
+
module ActiveSourceParts
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
# Accessor for addtional rdf types that will automatically be added to each
|
6
|
+
# object of that Source class
|
7
|
+
def additional_rdf_types
|
8
|
+
@additional_rdf_types ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
# New method for ActiveSources. If a URL of an existing Source is given as the only parameter,
|
12
|
+
# that source will be returned. This makes the class work smoothly with our ActiveRDF version
|
13
|
+
# query interface.
|
14
|
+
#
|
15
|
+
# Note that any semantic properties that were passed in to the constructor will be assigned
|
16
|
+
# *after* the ActiveRecord "create" callbacks have been called.
|
17
|
+
#
|
18
|
+
# The option hash may contain a "files" option, which can be used to add data files directly
|
19
|
+
# on creation. This will call the attach_files method on the object.
|
20
|
+
def new(*args)
|
21
|
+
the_source = if((args.size == 1) && (args.first.is_a?(Hash)))
|
22
|
+
# We have an option hash to init the source
|
23
|
+
files = args.first.delete(:files) || args.first.delete('files')
|
24
|
+
attributes = split_attribute_hash(args.first)
|
25
|
+
the_source = super(attributes[:db_attributes])
|
26
|
+
the_source.add_semantic_attributes(false, attributes[:semantic_attributes])
|
27
|
+
the_source.attach_files(files) if(files)
|
28
|
+
the_source
|
29
|
+
elsif(args.size == 1 && ( uri_s = uri_string_for(args[0]))) # One string argument should be the uri
|
30
|
+
# Either the current object from the db, or a new one if it doesn't exist in the db
|
31
|
+
find(:first, :conditions => { :uri => uri_s } ) || super(:uri => uri_s)
|
32
|
+
else
|
33
|
+
# In this case, it's a generic "new" call
|
34
|
+
super
|
35
|
+
end
|
36
|
+
the_source.add_additional_rdf_types if(the_source.new_record?)
|
37
|
+
the_source
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves a new source with the given type. This gets a propety hash
|
41
|
+
# like #new, but it will correctly initialize a source of the type given
|
42
|
+
# in the hash. If no type is given, this will create a plain ActiveSource.
|
43
|
+
def create_source(args)
|
44
|
+
type = args.delete(:type) || args.delete('type') || 'ActiveSource'
|
45
|
+
klass = type.constantize
|
46
|
+
klass.new(args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create sources from XML. The result is either a single source or an Array
|
50
|
+
# of sources, depending on wether the XML contains multiple sources.
|
51
|
+
#
|
52
|
+
# The imported sources will be saved during import, to ensure that relations
|
53
|
+
# between them are resolved correctly. If one of the imported elements
|
54
|
+
# does already exist, the existing source will be rewritten using ActiveSource#rewrite_attributes
|
55
|
+
#
|
56
|
+
# The options may contain:
|
57
|
+
#
|
58
|
+
# [*reader*] The reader class that the import should use
|
59
|
+
# [*progressor*] The progress reporting object, which must respond to run_with_progress(message, size, &block)
|
60
|
+
# [*errors*] If given, all erors will be looged to this array instead of raising
|
61
|
+
# an exception
|
62
|
+
def create_from_xml(xml, options = {})
|
63
|
+
reader = options[:reader] ? options[:reader].to_s.classify.constantize : TaliaCore::ActiveSourceParts::Xml::SourceReader
|
64
|
+
source_properties = reader.sources_from(xml, options[:progressor])
|
65
|
+
self.progressor = options[:progressor]
|
66
|
+
sources = create_multi_from(source_properties)
|
67
|
+
(sources.size > 1) ? sources : sources.first
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates multiple sources from the given array of attribute hashes. The
|
71
|
+
# sources are saved during import, ensuring that the relations are resolved
|
72
|
+
# correctly.
|
73
|
+
#
|
74
|
+
# Options:
|
75
|
+
# [*errors*] If given, all erors will be looged to this array instead of raising
|
76
|
+
# an exception
|
77
|
+
def create_multi_from(sources, options = {})
|
78
|
+
source_objects = []
|
79
|
+
run_with_progress('Writing imported', sources.size) do |progress|
|
80
|
+
source_objects = sources.collect do |props|
|
81
|
+
src = nil
|
82
|
+
begin
|
83
|
+
if(src = ActiveSource.find(:first, :conditions => { :uri => (props[:uri] || props['uri']) }))
|
84
|
+
# Deal with already existing sources
|
85
|
+
src.rewrite_attributes(props)
|
86
|
+
# Rewrite the type, if neccessary
|
87
|
+
type = props[:type] || props['type']
|
88
|
+
switch_type = type && (src.type != type)
|
89
|
+
# Warn to the log if we have a problematic type change
|
90
|
+
TaliaCore.logger.warn("WARNING: Type change from #{src.type} to #{type}") if(switch_type && !src.is_a?(DummySource))
|
91
|
+
src.type = type if(switch_type)
|
92
|
+
src
|
93
|
+
else
|
94
|
+
src = ActiveSource.create_source(props)
|
95
|
+
end
|
96
|
+
src.save!
|
97
|
+
rescue Exception => e
|
98
|
+
if(options[:errors])
|
99
|
+
options[:errors] << "ERROR during import of #{props['uri'] || props[:uri]}: #{e.message}"
|
100
|
+
TALIA_CORE.logger.warn("Problems importing #{props['uri'] || props[:uri]} (logged): #{e.message}")
|
101
|
+
else
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
end
|
105
|
+
progress.inc
|
106
|
+
src
|
107
|
+
end
|
108
|
+
end
|
109
|
+
source_objects
|
110
|
+
end
|
111
|
+
|
112
|
+
# This method is slightly expanded to allow passing uris and uri objects
|
113
|
+
# as an "id"
|
114
|
+
def exists?(value)
|
115
|
+
if(uri_s = uri_string_for(value))
|
116
|
+
super(:uri => uri_s)
|
117
|
+
else
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# Finder also accepts uris as "ids". There are also some additional options
|
124
|
+
# that are accepted:
|
125
|
+
#
|
126
|
+
# [*:find_through*] accepts and array with an predicate name and an object
|
127
|
+
# value/uri, to search for predicates that match the given predicate/value
|
128
|
+
# combination
|
129
|
+
# [*:type] specifically looks for sources with the given type.
|
130
|
+
# [*:find_through_inv*] like :find_through, but for the "inverse" lookup
|
131
|
+
# [*:prefetch_relations*] if set to "true", this will pre-load all semantic
|
132
|
+
# relations for the sources (experimental, not fully implemented yet)
|
133
|
+
def find(*args)
|
134
|
+
prefetching = false
|
135
|
+
if(args.last.is_a?(Hash))
|
136
|
+
options = args.last
|
137
|
+
prefetching = options.delete(:prefetch_relations)
|
138
|
+
if(options.empty?) # If empty we remove the args hash, so that the 1-param uri search works
|
139
|
+
args.pop
|
140
|
+
else
|
141
|
+
prepare_options!(args.last)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
result = if(args.size == 1 && (uri_s = uri_string_for(args[0])))
|
145
|
+
src = super(:first, :conditions => { :uri => uri_s })
|
146
|
+
raise(ActiveRecord::RecordNotFound, "Not found: #{uri_s}") unless(src)
|
147
|
+
src
|
148
|
+
else
|
149
|
+
super
|
150
|
+
end
|
151
|
+
|
152
|
+
prefetch_relations_for(result) if(prefetching)
|
153
|
+
|
154
|
+
result
|
155
|
+
end
|
156
|
+
|
157
|
+
# Semantic version of ActiveRecord::Base#update - the id may be a record id or an URL,
|
158
|
+
# and the attributes may contain semantic attributes. See the update_attributes method
|
159
|
+
# for details on how the semantic attributes behave.
|
160
|
+
def update(id, attributes)
|
161
|
+
record = find(id)
|
162
|
+
raise(ActiveRecord::RecordNotFound) unless(record)
|
163
|
+
record.update_attributes(attributes)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Like update, only that it will overwrite the given attributes instead
|
167
|
+
# of adding to them
|
168
|
+
def rewrite(id, attributes)
|
169
|
+
record = find(id)
|
170
|
+
raise(ActiveRecord::RecordNotFound) unless(record)
|
171
|
+
record.rewrite_attributes(attributes)
|
172
|
+
end
|
173
|
+
|
174
|
+
# The pagination will also use the prepare_options! to have access to the
|
175
|
+
# advanced finder options
|
176
|
+
def paginate(*args)
|
177
|
+
prepare_options!(args.last) if(args.last.is_a?(Hash))
|
178
|
+
super
|
179
|
+
end
|
180
|
+
|
181
|
+
# If will return itself unless the value is a SemanticProperty, in which
|
182
|
+
# case it will return the property's value.
|
183
|
+
def value_for(thing)
|
184
|
+
thing.is_a?(SemanticProperty) ? thing.value : thing
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns true if the given attribute is one that is stored in the database
|
188
|
+
def db_attr?(attribute)
|
189
|
+
db_attributes.include?(attribute.to_s)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Tries to expand a generic URI value that is either given as a full URL
|
193
|
+
# or a namespace:name value.
|
194
|
+
#
|
195
|
+
# This will assume a full URL if it finds a ":/" string inside the URI.
|
196
|
+
# Otherwise it will construct a namespace - name URI
|
197
|
+
def expand_uri(uri) # TODO: Merge with uri_for ?
|
198
|
+
assit_block do |errors|
|
199
|
+
unless(uri.respond_to?(:uri) || uri.kind_of?(String)) || uri.kind_of?(Symbol)
|
200
|
+
errors << "Found strange object of type #{uri.class}"
|
201
|
+
end
|
202
|
+
true
|
203
|
+
end
|
204
|
+
uri = uri.respond_to?(:uri) ? uri.uri.to_s : uri.to_s
|
205
|
+
return uri if(uri.include?(':/'))
|
206
|
+
N::URI.make_uri(uri).to_s
|
207
|
+
end
|
208
|
+
|
209
|
+
# Splits the attribute hash that is given for new, update and the like. This
|
210
|
+
# will return another hash, where result[:db_attributes] will contain the
|
211
|
+
# hash of the database attributes while result[:semantic_attributes] will
|
212
|
+
# contain the other attributes.
|
213
|
+
#
|
214
|
+
# The semantic attributes will be expanded to full URIs whereever possible.
|
215
|
+
#
|
216
|
+
# This method will *not* check for attributes that correspond to singular
|
217
|
+
# property names.
|
218
|
+
def split_attribute_hash(attributes)
|
219
|
+
assit_kind_of(Hash, attributes)
|
220
|
+
db_attributes = {}
|
221
|
+
semantic_attributes = {}
|
222
|
+
attributes.each do |field, value|
|
223
|
+
if(db_attr?(field))
|
224
|
+
db_attributes[field] = value
|
225
|
+
else
|
226
|
+
semantic_attributes[expand_uri(field)] = value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
{ :semantic_attributes => semantic_attributes, :db_attributes => db_attributes }
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
# The attributes stored in the database
|
235
|
+
def db_attributes
|
236
|
+
@db_attributes ||= ActiveSource.new.attribute_names
|
237
|
+
end
|
238
|
+
|
239
|
+
# Helper to define a "additional type" in subclasses which will
|
240
|
+
# automatically be added on Object creation
|
241
|
+
def has_rdf_type(*types)
|
242
|
+
@additional_rdf_types ||= []
|
243
|
+
types.each { |t| @additional_rdf_types << t.to_s }
|
244
|
+
end
|
245
|
+
|
246
|
+
# Helper to define a "singular accessor" for something (e.g. siglum, catalog)
|
247
|
+
# This accessor will provide an "accessor" method that returns the
|
248
|
+
# single property value directly and an assignment method that replaces
|
249
|
+
# the property with the value.
|
250
|
+
#
|
251
|
+
# The Source will cache newly set singular properties internally, so that
|
252
|
+
# the new value is immediately reflected on the object. However, the
|
253
|
+
# change will only be made permanent on #save! - and saving will also clear
|
254
|
+
# the cache
|
255
|
+
def singular_property(prop_name, property)
|
256
|
+
prop_name = prop_name.to_s
|
257
|
+
@singular_props ||= []
|
258
|
+
return if(@singular_props.include?(prop_name))
|
259
|
+
raise(ArgumentError, "Cannot overwrite method #{prop_name}") if(self.instance_methods.include?(prop_name) || self.instance_methods.include?("#{prop_name}="))
|
260
|
+
# define the accessor
|
261
|
+
define_method(prop_name) do
|
262
|
+
prop = self[property]
|
263
|
+
assit_block { |err| (prop.size > 1) ? err << "Must have at most 1 value for singular property #{prop_name} on #{self.uri}. Values #{self[property]}" : true }
|
264
|
+
prop.size > 0 ? prop[0] : nil
|
265
|
+
end
|
266
|
+
|
267
|
+
# define the writer
|
268
|
+
define_method("#{prop_name}=") do |value|
|
269
|
+
prop = self[property]
|
270
|
+
prop.remove
|
271
|
+
prop << value
|
272
|
+
end
|
273
|
+
|
274
|
+
# define the finder
|
275
|
+
(class << self ; self; end).module_eval do
|
276
|
+
define_method("find_by_#{prop_name}") do |value, *optional|
|
277
|
+
raise(ArgumentError, "Too many options") if(optional.size > 1)
|
278
|
+
options = optional.last || {}
|
279
|
+
finder = options.merge( :find_through => [property, value] )
|
280
|
+
find(:all, finder)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
@singular_props << prop_name
|
285
|
+
true
|
286
|
+
end
|
287
|
+
|
288
|
+
# Helper to creat an accessor for the given predicate. This will shortcut
|
289
|
+
# the prop_name method to self[property]
|
290
|
+
def simple_property(prop_name, property)
|
291
|
+
define_method(prop_name) do
|
292
|
+
self[property]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# This gets the URI string from the given value. This will just return
|
297
|
+
# the value if it's a string. It will return the result of value.uri, if
|
298
|
+
# that method exists; otherwise it'll return nil
|
299
|
+
def uri_string_for(value)
|
300
|
+
result = if value.is_a? String
|
301
|
+
# if this is a local name, prepend the local namespace
|
302
|
+
(value =~ /:/) ? value : (N::LOCAL + value).uri
|
303
|
+
elsif(value.respond_to?(:uri))
|
304
|
+
value.uri
|
305
|
+
else
|
306
|
+
nil
|
307
|
+
end
|
308
|
+
result = result.to_s if result
|
309
|
+
result
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
# Takes the "advanced" options that can be passed to the find method and
|
314
|
+
# converts them into "standard" find options.
|
315
|
+
def prepare_options!(options)
|
316
|
+
check_for_find_through!(options)
|
317
|
+
check_for_type_find!(options)
|
318
|
+
check_for_find_through_inv!(options)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Checks if the :find_through option is set. If so, this expects the
|
322
|
+
# option to have 2 values: The first representing the URL of the predicate
|
323
|
+
# and the second the URL or value that should be matched.
|
324
|
+
#
|
325
|
+
# An optional third parameter can be used to force an object search on the
|
326
|
+
# semantic_properties table (instead of active_sources) - if not present
|
327
|
+
# this will be auto-guessed from the "object value", checking if it appears
|
328
|
+
# to be an URL or not.
|
329
|
+
#
|
330
|
+
# ...find(:find_through => [N::RDF::something, 'value', true]
|
331
|
+
def check_for_find_through!(options)
|
332
|
+
if(f_through = options.delete(:find_through))
|
333
|
+
assit_kind_of(Array, f_through)
|
334
|
+
raise(ArgumentError, "Passed non-hash conditions with :find_through") if(options.has_key?(:conditions) && !options[:conditions].is_a?(Hash))
|
335
|
+
raise(ArgumentError, "Cannot pass custom join conditions with :find_through") if(options.has_key?(:joins))
|
336
|
+
predicate = f_through[0]
|
337
|
+
obj_val = f_through[1]
|
338
|
+
search_prop = (f_through.size > 2) ? f_through[2] : !(obj_val.to_s =~ /:/)
|
339
|
+
options[:joins] = default_joins(!search_prop, search_prop)
|
340
|
+
options[:conditions] ||= {}
|
341
|
+
options[:conditions]['semantic_relations.predicate_uri'] = predicate.to_s
|
342
|
+
if(search_prop)
|
343
|
+
options[:conditions]['obj_props.value'] = obj_val.to_s
|
344
|
+
else
|
345
|
+
options[:conditions]['obj_sources.uri'] = obj_val.to_s
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Check for the :find_through_inv option. This expects the 2 basic values
|
351
|
+
# in the same way as :find_through.
|
352
|
+
#
|
353
|
+
# find(:find_through_inv => [N::RDF::to_me, my_uri]
|
354
|
+
def check_for_find_through_inv!(options)
|
355
|
+
if(f_through = options.delete(:find_through_inv))
|
356
|
+
assit_kind_of(Array, f_through)
|
357
|
+
raise(ArgumentError, "Passed non-hash conditions with :find_through") if(options.has_key?(:conditions) && !options[:conditions].is_a?(Hash))
|
358
|
+
raise(ArgumentError, "Cannot pass custom join conditions with :find_through") if(options.has_key?(:joins))
|
359
|
+
options[:joins] = default_inv_joins
|
360
|
+
options[:conditions] ||= {}
|
361
|
+
options[:conditions]['semantic_relations.predicate_uri'] = f_through[0].to_s
|
362
|
+
options[:conditions]['sub_sources.uri'] = f_through[1].to_s
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
# Checks for the :type option in the find options. This is the same as
|
368
|
+
# doing a :find_through on the rdf type
|
369
|
+
def check_for_type_find!(options)
|
370
|
+
if(f_type = options.delete(:type))
|
371
|
+
options[:find_through] = [N::RDF::type, f_type.to_s, false]
|
372
|
+
check_for_find_through!(options)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module TaliaCore
|
2
|
+
module ActiveSourceParts
|
3
|
+
module PredicateHandler
|
4
|
+
# This file contains the handling of the "predicate wrapper" lists
|
5
|
+
# that represent the properties/objects a class has for a given predicate
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Attempts to fetch all relations on the given sources at once, so that
|
10
|
+
# there is potentially only one.
|
11
|
+
#
|
12
|
+
# For safety reasons, there is a limit on the number of sources that is
|
13
|
+
# accepted. (For a web application, if you go over the default, you're
|
14
|
+
# probably doing it wrong).
|
15
|
+
def prefetch_relations_for(sources, limit = 1024)
|
16
|
+
sources = [ sources ] if(sources.is_a?(ActiveSource))
|
17
|
+
raise(RangeError, "Too many sources for prefetching.") if(sources.size > limit)
|
18
|
+
src_hash = {}
|
19
|
+
sources.each { |src| src_hash[src.id] = src }
|
20
|
+
conditions = ['subject_id in (?)', src_hash.keys.join(', ')]
|
21
|
+
joins = ActiveSource.sources_join
|
22
|
+
joins << ActiveSource.props_join
|
23
|
+
relations = SemanticRelation.find(:all, :conditions => conditions,
|
24
|
+
:joins => joins,
|
25
|
+
:select => SemanticRelation.fat_record_select
|
26
|
+
)
|
27
|
+
relations.each do |rel|
|
28
|
+
src_hash[rel.subject_id].inject_predicate(rel)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set all as loaded
|
32
|
+
sources.each do |src|
|
33
|
+
src.each_cached_wrapper { |wrap| wrap.instance_variable_set(:'@loaded', true) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end # End class methods
|
38
|
+
|
39
|
+
# Gets the types
|
40
|
+
def types
|
41
|
+
get_objects_on(N::RDF.type.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the objects on the given predicate. This will be cached internally
|
45
|
+
# so that the object will always be the same as long as the parent source
|
46
|
+
# lives.
|
47
|
+
def get_objects_on(predicate)
|
48
|
+
@type_cache ||= {}
|
49
|
+
active_wrapper = @type_cache[predicate.to_s]
|
50
|
+
|
51
|
+
if(active_wrapper.nil?)
|
52
|
+
active_wrapper = SemanticCollectionWrapper.new(self, predicate)
|
53
|
+
@type_cache[predicate.to_s] = active_wrapper
|
54
|
+
end
|
55
|
+
|
56
|
+
active_wrapper
|
57
|
+
end
|
58
|
+
|
59
|
+
# Go through the existing relation wrappers and save the (new) items
|
60
|
+
def save_wrappers
|
61
|
+
each_cached_wrapper do |wrap|
|
62
|
+
# Load unloaded if we're not rdf_autosaving. Quick hack since otherwise
|
63
|
+
# since the blanking of unloaded properties could cause problems with
|
64
|
+
# the rdf writing otherwise
|
65
|
+
wrap.send(:load!) unless(wrap.loaded? || autosave_rdf?)
|
66
|
+
wrap.save_items!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Loops through the cache and passes each existing wrapper to the block
|
71
|
+
def each_cached_wrapper
|
72
|
+
return unless(@type_cache)
|
73
|
+
@type_cache.each_value { |wrap| yield(wrap) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Clear the source (this will force reloading of elements and discard
|
77
|
+
# unsaved changes
|
78
|
+
def reset!
|
79
|
+
@type_cache = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
# Injects a 'fat' predicate relation on a source
|
83
|
+
def inject_predicate(fat_relation)
|
84
|
+
wrapper = get_objects_on(fat_relation.predicate_uri)
|
85
|
+
wrapper.inject_fat_item(fat_relation)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module TaliaCore
|
2
|
+
module ActiveSourceParts
|
3
|
+
|
4
|
+
module Rdf
|
5
|
+
# This file contains the RDF handling elements of the ActiveSource class
|
6
|
+
|
7
|
+
# This can be used to turn of automatic rdf creation. *Attention:* Improperly
|
8
|
+
# used this may compromise the integrity of the RDF data. However, it may
|
9
|
+
# be used in order to speed up "create" operations that save a record
|
10
|
+
# several times and don't need the RDF data in the meantime.
|
11
|
+
def autosave_rdf?
|
12
|
+
@autosave_rdf = true unless(defined?(@autosave_rdf))
|
13
|
+
@autosave_rdf
|
14
|
+
end
|
15
|
+
|
16
|
+
# Set the autosave property. See autosave_rdf?
|
17
|
+
def autosave_rdf=(value)
|
18
|
+
@autosave_rdf = value
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the RDF object to use for this ActiveSource
|
22
|
+
def my_rdf
|
23
|
+
@rdf_resource ||= begin
|
24
|
+
src = RdfResource.new(uri)
|
25
|
+
src.object_class = TaliaCore::ActiveSource
|
26
|
+
src
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# This creates the RDF subgraph for this Source and saves it to disk. This
|
31
|
+
# may be an expensive operation since it removes the existing elements.
|
32
|
+
# (Could be optimised ;-)
|
33
|
+
#
|
34
|
+
# Unless the force option is specified, this will ignore predicates that
|
35
|
+
# remain unchanged. This means that writing will be faster if a predicate
|
36
|
+
# will not changed, but if database objects were not added through the
|
37
|
+
# standard API they'll be missed
|
38
|
+
#
|
39
|
+
# The force option may have three values: :false for normal operation,
|
40
|
+
# :force for forcing a complete rewrite and :create - the latter will
|
41
|
+
# avoid the cleaning of the elements in the RDF store in case the
|
42
|
+
# source is completely new and no triples exist.
|
43
|
+
def create_rdf(force = :false)
|
44
|
+
self.class.benchmark("\033[32m\033[4m\033[1mActiveSource::RD\033[0m Creating RDF for source", Logger::DEBUG, false) do
|
45
|
+
assit(!new_record?, "Record must exist here: #{self.uri}")
|
46
|
+
# Get the stuff to write. This will also erase the old data
|
47
|
+
|
48
|
+
s_rels = case force
|
49
|
+
when :force
|
50
|
+
prepare_all_predicates_to_write
|
51
|
+
when :create
|
52
|
+
prepare_predicates_to_create
|
53
|
+
else
|
54
|
+
prepare_predicates_to_write
|
55
|
+
end
|
56
|
+
s_rels.each do |sem_ref|
|
57
|
+
# We pass the object on. If it's a SemanticProperty, we need to add
|
58
|
+
# the value. If not the RDF handler will detect the #uri method and
|
59
|
+
# will add it as Resource.
|
60
|
+
obj = sem_ref.object
|
61
|
+
value = obj.is_a?(SemanticProperty) ? obj.value : obj
|
62
|
+
my_rdf.direct_write_predicate(N::URI.new(sem_ref.predicate_uri), value)
|
63
|
+
end
|
64
|
+
my_rdf.direct_write_predicate(N::RDF.type, (N::TALIA + self.class.name.demodulize))
|
65
|
+
my_rdf.save
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates an RDF/XML resprentation of the source
|
70
|
+
def to_rdf
|
71
|
+
rdf = String.new
|
72
|
+
|
73
|
+
ActiveSourceParts::Xml::RdfBuilder.open(:target => rdf, :indent => 2) do |builder|
|
74
|
+
builder.write_source(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
rdf
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Get the "standard" predicates to write (which is just the ones changed
|
83
|
+
# through the standard API. This will erase the
|
84
|
+
def prepare_predicates_to_write
|
85
|
+
preds_to_write = []
|
86
|
+
each_cached_wrapper do |wrap|
|
87
|
+
# If it wasn't loaded, it hasn't been written to
|
88
|
+
next if(wrap.clean?)
|
89
|
+
# Remove the existing data. TODO: Not using contexts
|
90
|
+
my_rdf.remove(N::URI.new(wrap.instance_variable_get(:@assoc_predicate)))
|
91
|
+
items = wrap.send(:items) # Get the items
|
92
|
+
items.each { |it| preds_to_write << it.relation }
|
93
|
+
end
|
94
|
+
preds_to_write
|
95
|
+
end
|
96
|
+
|
97
|
+
# This will get all existing predicates from the database. This will also
|
98
|
+
# erase the rdf for this source completely
|
99
|
+
# TODO: Could load with a single sql
|
100
|
+
def prepare_all_predicates_to_write
|
101
|
+
my_rdf.clear_rdf # TODO: Not using contexts here
|
102
|
+
SemanticRelation.find(:all, :conditions => { :subject_id => self.id })
|
103
|
+
end
|
104
|
+
|
105
|
+
# ATTENTION: This is a speed hack that avoids the usual checks based
|
106
|
+
# on the assumption that this source was created from scratch,
|
107
|
+
# no attributes are in the store and all attributes are in-memory.
|
108
|
+
def prepare_predicates_to_create
|
109
|
+
preds_to_create = []
|
110
|
+
each_cached_wrapper do |wrap|
|
111
|
+
next if(wrap.clean?)
|
112
|
+
# Evil, we get the items directly to avoid a useless load
|
113
|
+
items = wrap.instance_variable_get(:@items)
|
114
|
+
items.each { |it| preds_to_create << it.relation }
|
115
|
+
end
|
116
|
+
preds_to_create
|
117
|
+
end
|
118
|
+
|
119
|
+
def auto_update_rdf
|
120
|
+
create_rdf if(autosave_rdf?)
|
121
|
+
end
|
122
|
+
|
123
|
+
# On creation we force the full write, there's no use
|
124
|
+
# doing all checks if we know that nothing exists
|
125
|
+
def auto_create_rdf
|
126
|
+
create_rdf(:create) if(autosave_rdf?)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TaliaCore
|
2
|
+
module ActiveSourceParts
|
3
|
+
|
4
|
+
# This contains sql joins and segments that can be used by the ActiveSource classes.
|
5
|
+
module SqlHelper
|
6
|
+
|
7
|
+
# Returns the "default" join (meaning that it joins all the "triple tables"
|
8
|
+
# together. The flags signal whether the relations and properties should
|
9
|
+
# be joined.
|
10
|
+
def default_joins(include_rels = true, include_props = true)
|
11
|
+
join = "LEFT JOIN semantic_relations ON semantic_relations.subject_id = active_sources.id "
|
12
|
+
join << sources_join if(include_rels)
|
13
|
+
join << props_join if(include_props)
|
14
|
+
join
|
15
|
+
end
|
16
|
+
|
17
|
+
# Joins sources on semantic relations
|
18
|
+
def sources_join
|
19
|
+
" LEFT JOIN active_sources AS obj_sources ON semantic_relations.object_id = obj_sources.id AND semantic_relations.object_type = 'TaliaCore::ActiveSource'"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Joins properties on semantic relations
|
23
|
+
def props_join
|
24
|
+
" LEFT JOIN semantic_properties AS obj_props ON semantic_relations.object_id = obj_props.id AND semantic_relations.object_type = 'TaliaCore::SemanticProperty'"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the "default" join for reverse lookups
|
28
|
+
def default_inv_joins
|
29
|
+
join = "LEFT JOIN semantic_relations ON semantic_relations.object_id = active_sources.id AND semantic_relations.object_type = 'TaliaCore::ActiveSource' "
|
30
|
+
join << " LEFT JOIN active_sources AS sub_sources ON semantic_relations.subject_id = sub_sources.id"
|
31
|
+
join
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module TaliaCore
|
2
|
+
module ActiveSourceParts
|
3
|
+
module Xml
|
4
|
+
|
5
|
+
# Base class for builders that create source-related XML. This uses a Builder::XmlMarkup object
|
6
|
+
# in the background which does the actual XML writing.
|
7
|
+
#
|
8
|
+
# All builders will be used through the #open method, which can be passed either a Builder::XmlMarkup
|
9
|
+
# object, or the options to create one.
|
10
|
+
class BaseBuilder
|
11
|
+
|
12
|
+
# Creates a new builder. The options are equivalent for the options of the
|
13
|
+
# underlying Xml builder. The builder itself will be passed to the block that
|
14
|
+
# is called by this method.
|
15
|
+
# If you pass a :builder option instead, it will use the given builder instead
|
16
|
+
# of creating a new one
|
17
|
+
def self.open(options)
|
18
|
+
my_builder = self.new(options)
|
19
|
+
my_builder.send(:build_structure) do
|
20
|
+
yield(my_builder)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Quick helper: Returns the xml for one source as a string
|
25
|
+
def self.build_source(source)
|
26
|
+
xml = ''
|
27
|
+
|
28
|
+
open(:target => xml, :indent => 2) do |builder|
|
29
|
+
builder.write_source(source)
|
30
|
+
end
|
31
|
+
|
32
|
+
xml
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Create a new builder
|
38
|
+
def initialize(options)
|
39
|
+
@builder = options[:builder]
|
40
|
+
@builder ||= Builder::XmlMarkup.new(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|