talia_core 0.4.0

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