active-fedora 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-02-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,19 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ lib/active_fedora.rb
6
+ lib/active-fedora.rb
7
+ lib/active_fedora/base.rb
8
+ lib/active_fedora/content_model.rb
9
+ lib/active_fedora/datastream.rb
10
+ lib/active_fedora/fedora_object.rb
11
+ lib/active_fedora/metadata_datastream.rb
12
+ lib/active_fedora/model.rb
13
+ lib/active_fedora/property.rb
14
+ lib/active_fedora/qualified_dublin_core_datastream.rb
15
+ lib/active_fedora/relationship.rb
16
+ lib/active_fedora/rels_ext_datastream.rb
17
+ lib/active_fedora/semantic_node.rb
18
+ lib/active_fedora/solr_service.rb
19
+ solr/config/schema.xml
@@ -0,0 +1,7 @@
1
+
2
+ For more information on afed-regem, see http://afed-regem.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
@@ -0,0 +1,48 @@
1
+ = afed-regem
2
+
3
+ * FIX (url)
4
+
5
+ == DESCRIPTION:
6
+
7
+ FIX (describe your package)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2009 FIXME full name
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ require 'active_fedora'
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ gem 'ruby-fedora'
3
+ gem 'solr-ruby'
4
+ module ActiveFedora #:nodoc:
5
+ VERSION='1.0.0'
6
+ end
7
+
8
+
9
+
10
+ Dir[File.join(File.dirname(__FILE__), 'lib/active_fedora')+'/**/*.rb'].each{|x| "requiring #{x}";require x}
@@ -0,0 +1,362 @@
1
+ require 'util/class_level_inheritable_attributes'
2
+ require 'active_fedora/model'
3
+ require 'active_fedora/semantic_node'
4
+ module ActiveFedora
5
+
6
+ # This class ties together many of the lower-level modules, and
7
+ # implements something akin to an ActiveRecord-alike interface to
8
+ # fedora. If you want to represent a fedora object in the ruby
9
+ # space, this is the class you want to extend.
10
+ #
11
+ # =The Basics
12
+ # class Oralhistory < ActiveFedora::Base
13
+ # has_metadata :name => "properties", :type => ActiveFedora::MetadataDatastream do |m|
14
+ # m.field "narrator", :string
15
+ # m.field "narrator", :text
16
+ # end
17
+ # end
18
+ #
19
+ # The above example creates a FedoraObject with a metadata datastream named "properties", which is composed of a
20
+ # narrator and bio field.
21
+ #
22
+ # Datastreams defined with +has_metadata+ are accessed via the +datastreams+ member hash.
23
+ #
24
+ # =Implementation
25
+ # This class is really a facade for a basic Fedora::FedoraObject, which is stored internally.
26
+ class Base
27
+ include MediaShelfClassLevelInheritableAttributes
28
+ ms_inheritable_attributes :ds_specs
29
+ include Model
30
+ include SemanticNode
31
+
32
+ # Has this object been saved?
33
+ def new_object?
34
+ @new_object
35
+ end
36
+
37
+ # Constructor. If +attrs+ does not comtain +:pid+, we assume we're making a new one,
38
+ # and call off to the Fedora Rest API for the next available Fedora pid, and mark as new object.
39
+ #
40
+ # If there is a pid, we're re-hydrating an existing object, and new object is false. Once the @inner_object is stored,
41
+ # we configure any defined datastreams.
42
+ def initialize(attrs = {})
43
+ unless attrs[:pid]
44
+ attrs = attrs.merge!({:pid=>Fedora::Repository.instance.nextid})
45
+ @new_object=true
46
+ else
47
+ @new_object=false;
48
+ end
49
+ @inner_object = Fedora::FedoraObject.new(attrs)
50
+ @datastreams = {}
51
+ configure_defined_datastreams
52
+ end
53
+
54
+ #This method is used to specify the details of a datastream.
55
+ #args must include :name. Note that this method doesn't actually
56
+ #execute the block, but stores it at the class level, to be executed
57
+ #by any future instantiations.
58
+ def self.has_metadata(args, &block)
59
+ @ds_specs ||= Hash.new
60
+ @ds_specs[args[:name]]= [args[:type], block]
61
+ end
62
+
63
+ #Saves a Base object, and any dirty datastreams, then updates
64
+ #the Solr index for this object.
65
+ def save
66
+ # If it's a new object, set the conformsTo relationship for Fedora CMA
67
+ if new_object?
68
+ add_relationship(:conforms_to, ActiveFedora::ContentModel.pid_from_ruby_class(self.class))
69
+ end
70
+ @new_object =false
71
+ Fedora::Repository.instance.save(@inner_object)
72
+ datastreams_in_memory.each do |k,ds|
73
+ if ds.dirty? || ds.new_object?
74
+ if ds.kind_of?(ActiveFedora::MetadataDatastream)
75
+ metadata_is_dirty = true
76
+ end
77
+ ds.save
78
+ end
79
+ self.update_index if metadata_is_dirty == true
80
+ end
81
+ end
82
+
83
+ #Deletes a Base object, also deletes the info indexed in Solr, and
84
+ #the underlying inner_object.
85
+ def delete
86
+ Fedora::Repository.instance.delete(@inner_object)
87
+ escaped_pid = self.pid.gsub(/(:)/, '\\:')
88
+ SolrService.instance.conn.delete(escaped_pid)
89
+ end
90
+
91
+ # Returns all known datastreams for the object. If the object has been
92
+ # saved to fedora, the persisted datastreams will be included.
93
+ # Datastreams that have been modified in memory are given preference over
94
+ # the copy in Fedora.
95
+ def datastreams
96
+ if @new_object
97
+ @datastreams = datastreams_in_memory
98
+ else
99
+ @datastreams = datastreams_in_fedora.merge(datastreams_in_memory)
100
+ end
101
+
102
+ end
103
+
104
+ def datastreams_in_fedora #:nodoc:
105
+ mds = {}
106
+ self.datastreams_xml['datastream'].each do |ds|
107
+ ds.merge!({:pid => self.pid, :dsID => ds["dsid"]})
108
+ if ds["dsid"] == "RELS-EXT"
109
+ mds.merge!({ds["dsid"] => ActiveFedora::RelsExtDatastream.new(ds)})
110
+ else
111
+ mds.merge!({ds["dsid"] => ActiveFedora::Datastream.new(ds)})
112
+ end
113
+ mds[ds["dsid"]].new_object = false
114
+ end
115
+ mds
116
+ end
117
+
118
+ def datastreams_in_memory #:ndoc:
119
+ @datastreams ||= Hash.new
120
+ end
121
+
122
+ #return the datastream xml representation direclty from Fedora
123
+ def datastreams_xml
124
+ datastreams_xml = XmlSimple.xml_in(Fedora::Repository.instance.fetch_custom(self.pid, :datastreams))
125
+ end
126
+
127
+ # Adds datastream to the object. Saves the datastream to fedora upon adding.
128
+ def add_datastream(datastream)
129
+ datastream.pid = self.pid
130
+ datastreams[datastream.dsid] = datastream
131
+ return true
132
+ end
133
+ def add(datastream) # :nodoc:
134
+ warn "Warning: ActiveFedora::Base.add has been deprected. Use add_datastream"
135
+ add_datastream(datastream)
136
+ end
137
+
138
+ #return all datastreams of type ActiveFedora::MetadataDatastream
139
+ def metadata_streams
140
+ results = []
141
+ datastreams.each_value do |ds|
142
+ if ds.kind_of?(ActiveFedora::MetadataDatastream)
143
+ results<<ds
144
+ end
145
+ end
146
+ return results
147
+ end
148
+
149
+ #return all datastreams not of type ActiveFedora::MetadataDatastream
150
+ #(that aren't Dublin Core or RELS-EXT streams either)
151
+ def file_streams
152
+ results = []
153
+ datastreams.each_value do |ds|
154
+ if !ds.kind_of?(ActiveFedora::MetadataDatastream)
155
+ dsid = ds.dsid
156
+ if dsid != "DC" && dsid != "RELS-EXT"
157
+ results<<ds
158
+ end
159
+ end
160
+ end
161
+ return results
162
+ end
163
+
164
+ # Return the Dublin Core (DC) Datastream. You can also get at this via
165
+ # the +datastreams["DC"]+.
166
+ def dc
167
+ #dc = REXML::Document.new(datastreams["DC"].content)
168
+ return datastreams["DC"]
169
+ end
170
+
171
+ # Returns the RELS-EXT Datastream
172
+ # Tries to grab from in-memory datastreams first
173
+ # Failing that, attempts to load from Fedora and addst to in-memory datastreams
174
+ # Failing that, creates a new RelsExtDatastream and adds it to the object
175
+ def rels_ext
176
+ if !datastreams.has_key?("RELS-EXT")
177
+ add_datastream(ActiveFedora::RelsExtDatastream.new)
178
+ end
179
+ return datastreams["RELS-EXT"]
180
+ end
181
+
182
+ # @returns Hash of relationships, as defined by SemanticNode
183
+ # Rely on rels_ext datastream to track relationships array
184
+ # Overrides accessor for relationships array used by SemanticNode.
185
+ def relationships
186
+ return rels_ext.relationships
187
+ end
188
+
189
+ # Add a Rels-Ext relationship to the Object.
190
+ # @param predicate
191
+ # @param object Either a string URI or an object that responds to .pid
192
+ def add_relationship(predicate, obj)
193
+ #predicate = ActiveFedora::RelsExtDatastream.predicate_lookup(predicate)
194
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>obj)
195
+ rels_ext.add_relationship(r)
196
+ rels_ext.dirty = true
197
+ end
198
+
199
+
200
+ def inner_object # :nodoc
201
+ @inner_object
202
+ end
203
+
204
+ #return the pid of the Fedora Object
205
+ def pid
206
+ @inner_object.pid
207
+ end
208
+
209
+ #For Rails compatibility with url generators.
210
+ def to_param
211
+ self.pid
212
+ end
213
+ #return the internal fedora URI
214
+ def internal_uri
215
+ "info:fedora/#{pid}"
216
+ end
217
+
218
+ #return the state of the inner object
219
+ def state
220
+ @inner_object.state
221
+ end
222
+
223
+ #return the owner id
224
+ def owner_id
225
+ @inner_object.owner_id
226
+ end
227
+
228
+ #return the create_date of the inner object (unless it's a new object)
229
+ def create_date
230
+ @inner_object.create_date unless new_object?
231
+ end
232
+
233
+ #return the modification date of the inner object (unless it's a new object)
234
+ def modified_date
235
+ @inner_object.modified_date unless new_object?
236
+ end
237
+
238
+ #return the error list of the inner object (unless it's a new object)
239
+ def errors
240
+ @inner_object.errors
241
+ end
242
+
243
+ #return the label of the inner object (unless it's a new object)
244
+ def label
245
+ @inner_object.label
246
+ end
247
+
248
+
249
+ def self.deserialize(doc) #:nodoc:
250
+ pid = doc.elements['/foxml:digitalObject'].attributes['PID']
251
+ proto = self.new(:pid=>pid)
252
+ proto.datastreams.each do |name,ds|
253
+ doc.elements.each("//foxml:datastream[@ID='#{name}']") do |el|
254
+ proto.datastreams[name]=ds.class.from_xml(ds, el)
255
+ end
256
+ end
257
+ proto
258
+ end
259
+
260
+ #Return a hash of all available metadata fields for all
261
+ #ActiveFedora::MetadataDatastream datastreams, as well as
262
+ #system_create_date, system_modified_date, active_fedora_model_field,
263
+ #and the object id.
264
+ def fields
265
+ fields = {:id => {:values => [pid]}, :system_create_date => {:values => [self.create_date]}, :system_modified_date => {:values => [self.modified_date]}, :active_fedora_model_field => {:values => [self.class.inspect]}}
266
+ datastreams.values.each do |ds|
267
+ fields.merge!(ds.fields) if ds.kind_of?(ActiveFedora::MetadataDatastream)
268
+ end
269
+ return fields
270
+ end
271
+
272
+ #Returns the xml version of this object as a string.
273
+ def to_xml(xml=REXML::Document.new("<xml><fields/><content/></xml>"))
274
+ fields_xml = xml.root.elements['fields']
275
+ {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model_field => self.class.inspect}.each_pair do |attribute_name, value|
276
+ el = REXML::Element.new(attribute_name.to_s)
277
+ el.text = value
278
+ fields_xml << el
279
+ end
280
+ datastreams.each_value do |ds|
281
+ ds.to_xml(fields_xml) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream)
282
+ end
283
+ return xml.to_s
284
+ end
285
+
286
+ #Return a Solr::Document version of this object.
287
+ def to_solr(solr_doc = Solr::Document.new)
288
+ solr_doc << {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model_field => self.class.inspect}
289
+ datastreams.each_value do |ds|
290
+ solr_doc = ds.to_solr(solr_doc) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream)
291
+ end
292
+ return solr_doc
293
+ end
294
+
295
+ # Updates Solr index with self.
296
+ def update_index
297
+ SolrService.instance.conn.update(self.to_solr)
298
+ end
299
+
300
+ # An ActiveRecord-ism to udpate metadata values.
301
+ #
302
+ # Passing a hash into this method will attempt to set the values into the
303
+ # appropriate fields (for all datastreams, which means a.name and b.name
304
+ # fields are _both_ overwritten)
305
+ def update_attributes(params={})
306
+ params.each do |k,v|
307
+ datastreams.values.each do |d|
308
+ if d.fields[k.to_sym]
309
+ d.send("#{k}_values=", v)
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ # A convenience method for updating indexed attributes. The passed in hash
316
+ # must look like this :
317
+ # {{:name=>{"0"=>"a","1"=>"b"}}
318
+ #
319
+ # This will result in any datastream field of name :name having the value [a,b]
320
+ #
321
+ # An index of -1 will insert a new value. any existing value at the relevant index
322
+ # will be overwritten.
323
+ #
324
+ # As in update_attributes, this overwrites _all_ available fields.
325
+ #
326
+ def update_indexed_attributes(params={})
327
+ params.each do |key,value|
328
+ datastreams.each do |dsn, dstream|
329
+ if dstream.fields[key.to_sym]
330
+ aname="#{key}_values"
331
+ curval = dstream.send("#{aname}")
332
+ cpv=value.dup#copy this, we'll need the original for the next ds
333
+ cpv.delete_if do |y,z|
334
+ if curval[y.to_i] and y.to_i > -1
335
+ curval[y.to_i]=z
336
+ true
337
+ else
338
+ false
339
+ end
340
+ end
341
+ cpv.each { |y,z| curval<<z}#just append everything left
342
+ dstream.send("#{aname}=", curval) #write it back to the ds
343
+ end
344
+ end
345
+ end
346
+
347
+ end
348
+
349
+ private
350
+ def configure_defined_datastreams
351
+ if self.class.ds_specs
352
+ self.class.ds_specs.each do |name,ar|
353
+ ds = ar.first.new(:dsid=>name)
354
+ ar.last.call(ds)
355
+ self.add_datastream(ds)
356
+ end
357
+ end
358
+ end
359
+
360
+
361
+ end
362
+ end