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