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