moab-versioning 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/moab.rb +59 -0
  3. data/lib/moab/bagger.rb +289 -0
  4. data/lib/moab/config.rb +21 -0
  5. data/lib/moab/exceptions.rb +18 -0
  6. data/lib/moab/file_group.rb +244 -0
  7. data/lib/moab/file_group_difference.rb +336 -0
  8. data/lib/moab/file_group_difference_subset.rb +45 -0
  9. data/lib/moab/file_instance.rb +82 -0
  10. data/lib/moab/file_instance_difference.rb +54 -0
  11. data/lib/moab/file_inventory.rb +279 -0
  12. data/lib/moab/file_inventory_difference.rb +132 -0
  13. data/lib/moab/file_manifestation.rb +85 -0
  14. data/lib/moab/file_signature.rb +200 -0
  15. data/lib/moab/signature_catalog.rb +195 -0
  16. data/lib/moab/signature_catalog_entry.rb +61 -0
  17. data/lib/moab/storage_object.rb +220 -0
  18. data/lib/moab/storage_object_version.rb +333 -0
  19. data/lib/moab/storage_repository.rb +57 -0
  20. data/lib/moab/storage_services.rb +104 -0
  21. data/lib/moab/verification_result.rb +83 -0
  22. data/lib/moab/version_metadata.rb +38 -0
  23. data/lib/moab/version_metadata_entry.rb +64 -0
  24. data/lib/moab/version_metadata_event.rb +47 -0
  25. data/lib/moab_stanford.rb +18 -0
  26. data/lib/monkey_patches.rb +65 -0
  27. data/lib/serializer.rb +36 -0
  28. data/lib/serializer/manifest.rb +76 -0
  29. data/lib/serializer/serializable.rb +178 -0
  30. data/lib/stanford/active_fedora_object.rb +34 -0
  31. data/lib/stanford/content_inventory.rb +236 -0
  32. data/lib/stanford/dor_metadata.rb +49 -0
  33. data/lib/stanford/storage_repository.rb +46 -0
  34. data/lib/stanford/storage_services.rb +66 -0
  35. data/lib/tasks/yard.rake +34 -0
  36. data/lib/tools/api_doc_generator.rb +396 -0
  37. data/lib/tools/spec_generator.rb +410 -0
  38. data/lib/tools/spec_generator_old.rb +49 -0
  39. metadata +252 -0
@@ -0,0 +1,57 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # A class to represent the SDR repository store
6
+ #
7
+ # ====Data Model
8
+ # * <b>{StorageRepository} = represents a digital object repository storage node</b>
9
+ # * {StorageServices} = supports application layer access to the repository's objects, data, and metadata
10
+ # * {StorageObject} = represents a digital object's repository storage location and ingest/dissemination methods
11
+ # * {StorageObjectVersion} [1..*] = represents a version subdirectory within an object's home directory
12
+ # * {Bagger} [1] = utility for creating bagit packages for ingest or dissemination
13
+ #
14
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
15
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
16
+ class StorageRepository
17
+
18
+ # @return [Pathname] The location of the root directory of the repository storage node
19
+ def repository_home
20
+ Pathname.new(Moab::Config.repository_home)
21
+ end
22
+
23
+ # @param object_id [String] The identifier of the digital object whose version is desired
24
+ # @return [StorageObject] The representation of the desired object storage directory
25
+ def storage_object(object_id, create=false)
26
+ object_pathname = storage_object_pathname(object_id)
27
+ if object_pathname.exist?
28
+ StorageObject.new(object_id, object_pathname)
29
+ elsif create
30
+ object_pathname.mkpath
31
+ StorageObject.new(object_id, object_pathname)
32
+ else
33
+ raise Moab::ObjectNotFoundException, "No object found for #{object_id} at #{object_pathname}"
34
+ end
35
+ end
36
+
37
+ # @param object_id [String] The identifier of the digital object whose version is desired
38
+ # @return [Pathname] The location of the desired object's home directory (default=pairtree)
39
+ def storage_object_pathname(object_id)
40
+ #todo This method should be customized, or overridden in a subclass
41
+ # for a more sophisticated pairtree implementation see https://github.com/microservices/pairtree
42
+ path_segments = object_id.scan(/..?/) << object_id
43
+ object_path = path_segments.join(File::SEPARATOR).gsub(/:/,'_')
44
+ repository_home.join(object_path)
45
+ end
46
+
47
+ # @param druid [String] The object identifier
48
+ # @return [void] transfer the object to the preservation repository
49
+ def store_new_object_version(druid, bag_pathname )
50
+ storage_object = self.storage_object(druid, create=true)
51
+ new_version = storage_object.ingest_bag(bag_pathname)
52
+ new_version
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,104 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # An interface class to support access to SDR storage via a RESTful server
6
+ #
7
+ # ====Data Model
8
+ # * {StorageRepository} = represents a digital object repository storage node
9
+ # * <b>{StorageServices} = supports application layer access to the repository's objects, data, and metadata</b>
10
+ # * {StorageObject} = represents a digital object's repository storage location and ingest/dissemination methods
11
+ # * {StorageObjectVersion} [1..*] = represents a version subdirectory within an object's home directory
12
+ # * {Bagger} [1] = utility for creating bagit packages for ingest or dissemination
13
+ #
14
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
15
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
16
+ class StorageServices
17
+
18
+ # @return [StorageRepository] an instance of the interface to SDR storage
19
+ @@repository = Moab::StorageRepository.new
20
+
21
+ # @param object_id [String] The digital object identifier of the object
22
+ # @return [String] the location of the storage object
23
+ def self.object_path(object_id)
24
+ @@repository.storage_object(object_id).object_pathname.to_s
25
+ end
26
+
27
+ # @param object_id [String] The digital object identifier of the object
28
+ # @param [Integer] version_id The ID of the version, if nil use latest version
29
+ # @return [String] the location of the storage object version
30
+ def self.object_version_path(object_id,version_id=nil)
31
+ @@repository.storage_object(object_id).find_object_version(version_id).version_pathname.to_s
32
+ end
33
+
34
+ # @param object_id [String] The digital object identifier
35
+ # @return [Integer] The version number of the currently highest version
36
+ def self.current_version(object_id)
37
+ @@repository.storage_object(object_id).current_version_id
38
+ end
39
+
40
+ # @param [String] object_id The digital object identifier of the object
41
+ # @return [Pathname] Pathname object containing the full path for the specified file
42
+ def self.version_metadata(object_id)
43
+ self.retrieve_file('metadata', 'versionMetadata.xml', object_id)
44
+ end
45
+
46
+ # @param [String] object_id The digital object identifier of the object
47
+ # @param [Integer] version_id The ID of the version, if nil use latest version
48
+ # @return [FileInventory] the file inventory for the specified object version
49
+ def self.retrieve_file_group(file_category, object_id, version_id=nil)
50
+ storage_object_version = @@repository.storage_object(object_id).find_object_version(version_id)
51
+ if file_category =~ /manifest/
52
+ inventory_type = file_category = 'manifests'
53
+ else
54
+ inventory_type = 'version'
55
+ end
56
+ inventory = storage_object_version.file_inventory(inventory_type)
57
+ inventory.group(file_category)
58
+ end
59
+
60
+ # @param [String] file_category The category of file ('content', 'metdata', or 'manifest')
61
+ # @param [String] file_id The name of the file (path relative to base directory)
62
+ # @param [String] object_id The digital object identifier of the object
63
+ # @param [Integer] version_id The ID of the version, if nil use latest version
64
+ # @return [Pathname] Pathname object containing the full path for the specified file
65
+ def self.retrieve_file(file_category, file_id, object_id, version_id=nil)
66
+ storage_object_version = @@repository.storage_object(object_id).find_object_version(version_id)
67
+ file_pathname = storage_object_version.find_filepath(file_category, file_id)
68
+ end
69
+
70
+ # @param [String] file_category The category of file ('content', 'metdata', or 'manifest')
71
+ # @param [FileSignature] file_signature The signature of the file
72
+ # @param [String] object_id The digital object identifier of the object
73
+ # @param [Integer] version_id The ID of the version, if nil use latest version
74
+ # @return [Pathname] Pathname object containing the full path for the specified file
75
+ def self.retrieve_file_using_signature(file_category, file_signature, object_id, version_id=nil)
76
+ storage_object_version = @@repository.storage_object(object_id).find_object_version(version_id)
77
+ file_pathname = storage_object_version.find_filepath_using_signature(file_category, file_signature)
78
+ end
79
+
80
+ # @param [String] file_category The category of file ('content', 'metdata', or 'manifest')
81
+ # @param [String] file_id The name of the file (path relative to base directory)
82
+ # @param [String] object_id The digital object identifier of the object
83
+ # @param [Integer] version_id The ID of the version, if nil use latest version
84
+ # @return [FileSignature] The signature of the file
85
+ def self.retrieve_file_signature(file_category, file_id, object_id, version_id=nil)
86
+ storage_object_version = @@repository.storage_object(object_id).find_object_version(version_id)
87
+ file_pathname = storage_object_version.find_signature(file_category, file_id)
88
+ end
89
+
90
+ # @param [String] object_id The digital object identifier of the object
91
+ # @param [Object] base_version_id The identifier of the base version to be compared
92
+ # @param [Object] compare_version_id The identifier of the version to be compared to the base version
93
+ # @return [FileInventoryDifference] The report of the version differences
94
+ def self.version_differences(object_id, base_version_id,compare_version_id)
95
+ base_version = @@repository.storage_object(object_id).storage_object_version(base_version_id)
96
+ compare_version = @@repository.storage_object(object_id).storage_object_version(compare_version_id)
97
+ base_inventory=base_version.file_inventory('version')
98
+ compare_inventory=compare_version.file_inventory('version')
99
+ FileInventoryDifference.new.compare(base_inventory,compare_inventory)
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,83 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ class VerificationResult
6
+
7
+ # @return [String] The name of the entity that was verified
8
+ attr_accessor :entity
9
+
10
+ # @return [Boolean] The true/false outcome of the verification
11
+ attr_accessor :verified
12
+
13
+ # @return [Hash] The details of the comparisons that were made
14
+ attr_accessor :details
15
+
16
+ # @return [Array<VerificationResult>] The subentities, if any, on which this verification is based
17
+ attr_accessor :subentities
18
+
19
+ # @param entity [Object] The name of the entity being verified
20
+ def initialize(entity)
21
+ @entity = entity
22
+ @verified = false
23
+ @details = nil
24
+ @subentities = Array.new
25
+ end
26
+
27
+ # @param entity [Symbol] The name of the entity being verified
28
+ # @param expected [Object] The expected value
29
+ # @param found [Object] The found value
30
+ # @return [VerificationResult] The result of comparing the expected and found values
31
+ def self.verify_value(entity, expected, found)
32
+ result = VerificationResult.new(entity.to_s)
33
+ result.verified = (expected == found)
34
+ result.details = {'expected' => expected, 'found' => found}
35
+ result
36
+ end
37
+
38
+ # @param entity [Symbol] The name of the entity being verified
39
+ # @param expression [Object] The expression that will be evaluated as true or false
40
+ # @param details [Object] optional details that could be reported
41
+ # @return [VerificationResult] The result of evaluating the expression
42
+ def self.verify_truth(entity,expression,details=nil)
43
+ result = VerificationResult.new(entity.to_s)
44
+ result.verified = !(expression.nil? or (expression == false))
45
+ result.details = details
46
+ result
47
+ end
48
+
49
+ # @param verbose [Boolean] If true, always provide all details of the verification
50
+ # @return [String] The verification result serialized to JSON
51
+ def to_json(verbose=false)
52
+ JSON.pretty_generate(to_hash(verbose))
53
+ end
54
+
55
+ # @param verbose [Boolean] If true, always provide all details of the verification
56
+ # @param level [Integer] Used to test the depth of recursion
57
+ # @return [OrderedHash] The verification result serialized to a hash
58
+ def to_hash(verbose=false,level=0)
59
+ hash = OrderedHash.new
60
+ hash['verified'] = @verified
61
+ if (verbose or @verified == false)
62
+ hash['details'] = @details ? @details : subentities_to_hash(verbose,level)
63
+ end
64
+ if level > 0
65
+ hash
66
+ else
67
+ {@entity => hash}
68
+ end
69
+ end
70
+
71
+ # @param verbose [Boolean] If true, always provide all details of the verification
72
+ # @param level [Integer] Used to increment the depth of recursion
73
+ # @return [OrderedHash] The verification result of subentities serialized to a hash
74
+ def subentities_to_hash(verbose,level)
75
+ hash = OrderedHash.new
76
+ @subentities.each do |s|
77
+ hash[s.entity] = s.to_hash(verbose, level+1)
78
+ end
79
+ hash
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,38 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # The descriptive information about a digital object's collection of versions
6
+ #
7
+ # ====Data Model
8
+ # * <b>{VersionMetadata} = descriptive information about a digital object's versions</b>
9
+ # * {VersionMetadataEntry} [1..*] = attributes of a digital object version
10
+ # * {VersionMetadataEvent} [1..*] = object version lifecycle events with timestamps
11
+ #
12
+ # @example {include:file:spec/fixtures/data/jq937jp0017/v3/metadata/versionMetadata.xml}
13
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
14
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
15
+ class VersionMetadata < Manifest
16
+
17
+ include HappyMapper
18
+
19
+ # The name of the XML element used to serialize this objects data
20
+ tag 'versionMetadata'
21
+
22
+ # (see Serializable#initialize)
23
+ def initialize(opts={})
24
+ @versions = Array.new
25
+ super(opts)
26
+ end
27
+
28
+ # @attribute
29
+ # @return [String] The digital object identifier
30
+ attribute :digital_object_id, String, :tag => 'objectId'
31
+
32
+ # @attribute
33
+ # @return [Array<VersionMetadataEntry>] An array of version metadata entries, one per version
34
+ has_many :versions, VersionMetadataEntry, :tag => 'version'
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,64 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # The descriptive attributes of a digital object version.
6
+ #
7
+ # ====Data Model
8
+ # * {VersionMetadata} = descriptive information about a digital object's versions
9
+ # * <b>{VersionMetadataEntry} [1..*] = attributes of a digital object version</b>
10
+ # * {VersionMetadataEvent} [1..*] = object version lifecycle events with timestamps
11
+ #
12
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
13
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
14
+ class VersionMetadataEntry < Serializable
15
+
16
+ include HappyMapper
17
+
18
+ # The name of the XML element used to serialize this objects data
19
+ tag 'version'
20
+
21
+ # (see Serializable#initialize)
22
+ def initialize(opts={})
23
+ @events = Array.new
24
+ super(opts)
25
+ end
26
+
27
+ # @attribute
28
+ # @return [Integer] The object version number (A sequential integer)
29
+ attribute :version_id, Integer, :tag => 'versionId', :key => true, :on_save => Proc.new {|n| n.to_s}
30
+
31
+ # @attribute
32
+ # @return [String] "an external version label that increments the most significant digit for major revisions,
33
+ # second digit for minor revisions, and third for admin? e.g., 1.0, 1.1, 2.0, 2.0.1 etc.
34
+ # This should be dynamically derivable across the set of versions
35
+ attribute :label, String
36
+
37
+ # @attribute
38
+ # @return [String] "major|minor|admin"
39
+ attribute :significance, String
40
+
41
+ # @attribute
42
+ # @return [String] A free text external description of why the version was created
43
+ element :description, String
44
+
45
+ # @attribute
46
+ # @return [String] An internal annotation summarizing the changes (optional)
47
+ element :note, String
48
+
49
+ # @attribute
50
+ # @return [FileGroupDifference] Summary of content file differences since previous version
51
+ element :content_changes, FileGroupDifference, :tag => 'fileGroupDifference'
52
+
53
+ # @attribute
54
+ # @return [FileGroupDifference] Summary of metadata file differences since previous version
55
+ element :metadata_changes, FileGroupDifference, :tag => 'fileGroupDifference'
56
+
57
+ # @attribute
58
+ # @return [Array<VersionMetadataEvent>] Array of events with timestamps that track lifecycle stages
59
+ has_many :events, VersionMetadataEvent, :tag => 'event'
60
+
61
+ end
62
+
63
+ end
64
+
@@ -0,0 +1,47 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # A container element to record object version lifecycle events with timestamps
6
+ #
7
+ # ====Data Model
8
+ # * {VersionMetadata} = descriptive information about a digital object's versions
9
+ # * {VersionMetadataEntry} [1..*] = attributes of a digital object version
10
+ # * <b>{VersionMetadataEvent} [1..*] = object version lifecycle events with timestamps</b>
11
+ #
12
+ # @see VersionMetadata
13
+ # @see VersionMetadataEntry
14
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
15
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
16
+ class VersionMetadataEvent < Serializable
17
+
18
+ include HappyMapper
19
+
20
+ # The name of the XML element used to serialize this objects data
21
+ tag 'event'
22
+
23
+ # (see Serializable#initialize)
24
+ def initialize(opts={})
25
+ super(opts)
26
+ end
27
+
28
+ # @attribute
29
+ # @return [String] The type of event
30
+ attribute :type, String
31
+
32
+
33
+ # @attribute
34
+ # @return [Time] The date and time of an event
35
+ attribute :datetime, Time, :on_save => Proc.new {|t| t.to_s}
36
+
37
+ def datetime=(event_datetime)
38
+ @datetime=Time.input(event_datetime)
39
+ end
40
+
41
+ def datetime
42
+ Time.output(@datetime)
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,18 @@
1
+ require 'moab'
2
+ include Moab
3
+ require 'stanford/content_inventory'
4
+ require 'stanford/dor_metadata'
5
+ require 'stanford/storage_repository'
6
+ require 'stanford/storage_services'
7
+ require 'stanford/active_fedora_object'
8
+
9
+ # Stanford is a module that isolates classes specific to the Stanford Digital Repository
10
+ #
11
+ # ====Data Model
12
+ # * <b>{DorMetadata} = utility methods for interfacing with Stanford metadata files (esp contentMetadata)</b>
13
+ # * {ActiveFedoraObject} [1..*] = utility for extracting content or other information from a Fedora Instance
14
+ #
15
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
16
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
17
+ module Stanford
18
+ end
@@ -0,0 +1,65 @@
1
+ require 'time'
2
+
3
+ # monkey-patch to fix JSON_pure
4
+ # @see http://prettystatemachine.blogspot.com/2010/09/typeerrors-in-tojson-make-me-briefly.html
5
+ #noinspection RubyUnusedLocalVariable
6
+ class Fixnum
7
+ # @return [String] The string value of the number
8
+ def to_json(options = nil)
9
+ to_s
10
+ end
11
+ end
12
+
13
+ # Make the default output from a Time variable be formatted in ISO 8601 format
14
+ class Time
15
+ # @return [String] The datetime in ISO 8601 format
16
+ def to_s
17
+ self.utc.iso8601
18
+ end
19
+
20
+ # @param datetime [Time,String,Nil] The input datetime
21
+ # @return [void] Store the input datetime as a Time object
22
+ def self.input(datetime)
23
+ case datetime
24
+ when nil
25
+ nil
26
+ when String
27
+ Time.parse(datetime)
28
+ when Time
29
+ datetime
30
+ else
31
+ raise "unknown time format #{datetime.inspect}"
32
+ end
33
+ end
34
+
35
+ # @param datetime [Time,String,Nil] The datetime value to output
36
+ # @return [String] Format the datetime into a ISO 8601 formatted string
37
+ def self.output(datetime)
38
+ case datetime
39
+ when nil
40
+ nil
41
+ when String
42
+ Time.parse(datetime).utc.iso8601
43
+ when Time
44
+ datetime.utc.iso8601
45
+ else
46
+ raise "unknown time format #{datetime.inspect}"
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ # Add methods to Array so that one attribute of a class can be treated as a unique key
53
+ class Array
54
+ # @return [Array<Object>] An array containing the key fields from a collection of objects
55
+ def keys
56
+ self.collect { |e| e.key }
57
+ end
58
+
59
+ # @param key [Object] The key value to search for
60
+ # @return [Object] The object from the array that has the specified key value
61
+ def keyfind(key)
62
+ self.each { |e| return e if key == e.key }
63
+ nil
64
+ end
65
+ end