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.
Files changed (119) hide show
  1. data/README.rdoc +41 -0
  2. data/bin/talia +33 -0
  3. data/lib/JXslt/jxslt.rb +60 -0
  4. data/lib/acts_as_roled.rb +11 -0
  5. data/lib/core_ext/platform.rb +9 -0
  6. data/lib/core_ext/string.rb +6 -0
  7. data/lib/core_ext.rb +1 -0
  8. data/lib/custom_template.rb +4 -0
  9. data/lib/loader_helper.rb +62 -0
  10. data/lib/mysql.rb +1214 -0
  11. data/lib/progressbar.rb +236 -0
  12. data/lib/role.rb +12 -0
  13. data/lib/talia_cl/command_line.rb +39 -0
  14. data/lib/talia_cl/commands/standalone/cl_options.rb +9 -0
  15. data/lib/talia_cl/commands/standalone/standalone_generate.rb +75 -0
  16. data/lib/talia_cl/commands/standalone.rb +25 -0
  17. data/lib/talia_cl/commands/talia_console/cl_options.rb +55 -0
  18. data/lib/talia_cl/commands/talia_console/console_commands.rb +37 -0
  19. data/lib/talia_cl/commands/talia_console/talia_commands.rb +131 -0
  20. data/lib/talia_cl/commands/talia_console.rb +47 -0
  21. data/lib/talia_cl/core_commands.rb +11 -0
  22. data/lib/talia_cl.rb +47 -0
  23. data/lib/talia_core/active_source.rb +372 -0
  24. data/lib/talia_core/active_source_parts/class_methods.rb +378 -0
  25. data/lib/talia_core/active_source_parts/predicate_handler.rb +89 -0
  26. data/lib/talia_core/active_source_parts/rdf.rb +131 -0
  27. data/lib/talia_core/active_source_parts/sql_helper.rb +36 -0
  28. data/lib/talia_core/active_source_parts/xml/base_builder.rb +47 -0
  29. data/lib/talia_core/active_source_parts/xml/generic_reader.rb +363 -0
  30. data/lib/talia_core/active_source_parts/xml/rdf_builder.rb +88 -0
  31. data/lib/talia_core/active_source_parts/xml/source_builder.rb +73 -0
  32. data/lib/talia_core/active_source_parts/xml/source_reader.rb +20 -0
  33. data/lib/talia_core/agent.rb +14 -0
  34. data/lib/talia_core/background_jobs/job.rb +82 -0
  35. data/lib/talia_core/background_jobs/progress_job.rb +68 -0
  36. data/lib/talia_core/collection.rb +13 -0
  37. data/lib/talia_core/data_types/data_loader.rb +92 -0
  38. data/lib/talia_core/data_types/data_record.rb +105 -0
  39. data/lib/talia_core/data_types/delayed_copier.rb +76 -0
  40. data/lib/talia_core/data_types/file_record.rb +59 -0
  41. data/lib/talia_core/data_types/file_store.rb +306 -0
  42. data/lib/talia_core/data_types/iip_data.rb +153 -0
  43. data/lib/talia_core/data_types/iip_loader.rb +127 -0
  44. data/lib/talia_core/data_types/image_data.rb +32 -0
  45. data/lib/talia_core/data_types/media_link.rb +19 -0
  46. data/lib/talia_core/data_types/mime_mapping.rb +45 -0
  47. data/lib/talia_core/data_types/path_helpers.rb +77 -0
  48. data/lib/talia_core/data_types/pdf_data.rb +42 -0
  49. data/lib/talia_core/data_types/simple_text.rb +36 -0
  50. data/lib/talia_core/data_types/temp_file_handling.rb +85 -0
  51. data/lib/talia_core/data_types/xml_data.rb +169 -0
  52. data/lib/talia_core/dc_resource.rb +20 -0
  53. data/lib/talia_core/dummy_handler.rb +34 -0
  54. data/lib/talia_core/dummy_source.rb +20 -0
  55. data/lib/talia_core/errors.rb +25 -0
  56. data/lib/talia_core/initializer.rb +427 -0
  57. data/lib/talia_core/ordered_source.rb +228 -0
  58. data/lib/talia_core/rails_ext/actionpack/action_controller/record_identifier.rb +13 -0
  59. data/lib/talia_core/rails_ext/actionpack/action_controller.rb +1 -0
  60. data/lib/talia_core/rails_ext/actionpack.rb +1 -0
  61. data/lib/talia_core/rails_ext.rb +1 -0
  62. data/lib/talia_core/rdf_import.rb +90 -0
  63. data/lib/talia_core/rdf_resource.rb +159 -0
  64. data/lib/talia_core/semantic_collection_item.rb +93 -0
  65. data/lib/talia_core/semantic_collection_wrapper.rb +324 -0
  66. data/lib/talia_core/semantic_property.rb +7 -0
  67. data/lib/talia_core/semantic_relation.rb +67 -0
  68. data/lib/talia_core/source.rb +323 -0
  69. data/lib/talia_core/source_transfer_object.rb +38 -0
  70. data/lib/talia_core/workflow/base.rb +15 -0
  71. data/lib/talia_core/workflow/publication_workflow.rb +62 -0
  72. data/lib/talia_core/workflow.rb +300 -0
  73. data/lib/talia_core.rb +9 -0
  74. data/lib/talia_dependencies.rb +12 -0
  75. data/lib/talia_util/bar_progressor.rb +15 -0
  76. data/lib/talia_util/configuration/config_file.rb +48 -0
  77. data/lib/talia_util/configuration/database_config.rb +40 -0
  78. data/lib/talia_util/configuration/mysql_database_setup.rb +104 -0
  79. data/lib/talia_util/data_import.rb +91 -0
  80. data/lib/talia_util/image_conversions.rb +82 -0
  81. data/lib/talia_util/import_job_helper.rb +132 -0
  82. data/lib/talia_util/io_helper.rb +54 -0
  83. data/lib/talia_util/progressable.rb +38 -0
  84. data/lib/talia_util/progressbar.rb +236 -0
  85. data/lib/talia_util/rdf_update.rb +80 -0
  86. data/lib/talia_util/some_sigla.xml +1960 -0
  87. data/lib/talia_util/test_helpers.rb +151 -0
  88. data/lib/talia_util/util.rb +226 -0
  89. data/lib/talia_util/yaml_import.rb +80 -0
  90. data/lib/talia_util.rb +13 -0
  91. data/lib/user.rb +116 -0
  92. data/lib/version.rb +15 -0
  93. data/test/core_ext/string_test.rb +11 -0
  94. data/test/custom_template_test.rb +8 -0
  95. data/test/talia_core/active_source_predicate_test.rb +54 -0
  96. data/test/talia_core/active_source_rdf_test.rb +89 -0
  97. data/test/talia_core/active_source_test.rb +631 -0
  98. data/test/talia_core/data_types/data_loader_test.rb +123 -0
  99. data/test/talia_core/data_types/data_record_test.rb +40 -0
  100. data/test/talia_core/data_types/file_record_test.rb +171 -0
  101. data/test/talia_core/data_types/iip_data_test.rb +130 -0
  102. data/test/talia_core/data_types/image_data_test.rb +88 -0
  103. data/test/talia_core/data_types/pdf_data_test.rb +68 -0
  104. data/test/talia_core/data_types/xml_data_test.rb +134 -0
  105. data/test/talia_core/generic_xml_test.rb +83 -0
  106. data/test/talia_core/initializer_test.rb +36 -0
  107. data/test/talia_core/ordered_source_test.rb +398 -0
  108. data/test/talia_core/rdf_resource_test.rb +115 -0
  109. data/test/talia_core/semantic_collection_item_test.rb +129 -0
  110. data/test/talia_core/source_reader_test.rb +33 -0
  111. data/test/talia_core/source_test.rb +484 -0
  112. data/test/talia_core/source_transfer_object_test.rb +24 -0
  113. data/test/talia_core/workflow/publication_workflow_test.rb +242 -0
  114. data/test/talia_core/workflow/user_class_for_workflow.rb +35 -0
  115. data/test/talia_core/workflow/workflow_base_test.rb +21 -0
  116. data/test/talia_core/workflow_test.rb +19 -0
  117. data/test/talia_util/import_job_helper_test.rb +46 -0
  118. data/test/test_helper.rb +68 -0
  119. 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