longleaf 0.1.0.pre.2 → 1.0.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +94 -0
  3. data/.editorconfig +13 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +44 -0
  6. data/.rubocop_todo.yml +834 -0
  7. data/.yardopts +1 -0
  8. data/Gemfile +16 -1
  9. data/README.md +98 -12
  10. data/Rakefile +6 -0
  11. data/bin/setup +16 -1
  12. data/docs/aboutlongleaf.md +28 -0
  13. data/docs/extra.css +32 -0
  14. data/docs/img/change-file.png +0 -0
  15. data/docs/img/ll-example-preserved.png +0 -0
  16. data/docs/index.md +19 -0
  17. data/docs/install.md +66 -0
  18. data/docs/ll-example/config-example-relative.yml +33 -0
  19. data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
  20. data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
  21. data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
  22. data/docs/ll-example/metadata-dir/.gitkeep +0 -0
  23. data/docs/ll-example/replica-files/.gitkeep +0 -0
  24. data/docs/ll-example/replica-metadata/.gitkeep +0 -0
  25. data/docs/quickstart.md +270 -0
  26. data/docs/rdocs/Longleaf.html +135 -0
  27. data/docs/rdocs/Longleaf/AppFields.html +178 -0
  28. data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
  29. data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
  30. data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
  31. data/docs/rdocs/Longleaf/CLI.html +909 -0
  32. data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
  33. data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
  34. data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
  35. data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
  36. data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
  37. data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
  38. data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
  39. data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
  40. data/docs/rdocs/Longleaf/EventError.html +147 -0
  41. data/docs/rdocs/Longleaf/EventNames.html +163 -0
  42. data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
  43. data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
  44. data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
  45. data/docs/rdocs/Longleaf/FileRecord.html +716 -0
  46. data/docs/rdocs/Longleaf/FileSelector.html +901 -0
  47. data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
  48. data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
  49. data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
  50. data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
  51. data/docs/rdocs/Longleaf/Logging.html +405 -0
  52. data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
  53. data/docs/rdocs/Longleaf/LongleafError.html +139 -0
  54. data/docs/rdocs/Longleaf/MDFields.html +193 -0
  55. data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
  56. data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
  57. data/docs/rdocs/Longleaf/MetadataError.html +143 -0
  58. data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
  59. data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
  60. data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
  61. data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
  62. data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
  63. data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
  64. data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
  65. data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
  66. data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
  67. data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
  68. data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
  69. data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
  70. data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
  71. data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
  72. data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
  73. data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
  74. data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
  75. data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
  76. data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
  77. data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
  78. data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
  79. data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
  80. data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
  81. data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
  82. data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
  83. data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
  84. data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
  85. data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
  86. data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
  87. data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
  88. data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
  89. data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
  90. data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
  91. data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
  92. data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
  93. data/docs/rdocs/_index.html +660 -0
  94. data/docs/rdocs/class_list.html +51 -0
  95. data/docs/rdocs/css/common.css +1 -0
  96. data/docs/rdocs/css/full_list.css +58 -0
  97. data/docs/rdocs/css/style.css +496 -0
  98. data/docs/rdocs/file.README.html +165 -0
  99. data/docs/rdocs/file_list.html +56 -0
  100. data/docs/rdocs/frames.html +17 -0
  101. data/docs/rdocs/index.html +165 -0
  102. data/docs/rdocs/js/app.js +303 -0
  103. data/docs/rdocs/js/full_list.js +216 -0
  104. data/docs/rdocs/js/jquery.js +4 -0
  105. data/docs/rdocs/method_list.html +2051 -0
  106. data/docs/rdocs/top-level-namespace.html +110 -0
  107. data/lib/longleaf/candidates/file_selector.rb +139 -0
  108. data/lib/longleaf/candidates/manifest_digest_provider.rb +17 -0
  109. data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
  110. data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +93 -0
  111. data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
  112. data/lib/longleaf/candidates/service_candidate_locator.rb +23 -0
  113. data/lib/longleaf/candidates/single_digest_provider.rb +13 -0
  114. data/lib/longleaf/cli.rb +237 -46
  115. data/lib/longleaf/commands/deregister_command.rb +51 -0
  116. data/lib/longleaf/commands/preserve_command.rb +50 -0
  117. data/lib/longleaf/commands/register_command.rb +32 -43
  118. data/lib/longleaf/commands/reindex_command.rb +92 -0
  119. data/lib/longleaf/commands/validate_config_command.rb +33 -8
  120. data/lib/longleaf/commands/validate_metadata_command.rb +51 -0
  121. data/lib/longleaf/errors.rb +26 -7
  122. data/lib/longleaf/events/deregister_event.rb +53 -0
  123. data/lib/longleaf/events/event_names.rb +9 -0
  124. data/lib/longleaf/events/event_status_tracking.rb +59 -0
  125. data/lib/longleaf/events/preserve_event.rb +81 -0
  126. data/lib/longleaf/events/register_event.rb +52 -51
  127. data/lib/longleaf/helpers/case_insensitive_hash.rb +38 -0
  128. data/lib/longleaf/helpers/digest_helper.rb +56 -0
  129. data/lib/longleaf/helpers/s3_uri_helper.rb +86 -0
  130. data/lib/longleaf/helpers/selection_options_parser.rb +189 -0
  131. data/lib/longleaf/helpers/service_date_helper.rb +78 -0
  132. data/lib/longleaf/indexing/index_manager.rb +101 -0
  133. data/lib/longleaf/indexing/sequel_index_driver.rb +306 -0
  134. data/lib/longleaf/logging.rb +5 -4
  135. data/lib/longleaf/logging/redirecting_logger.rb +26 -25
  136. data/lib/longleaf/models/app_fields.rb +7 -2
  137. data/lib/longleaf/models/file_record.rb +17 -8
  138. data/lib/longleaf/models/filesystem_metadata_location.rb +56 -0
  139. data/lib/longleaf/models/filesystem_storage_location.rb +52 -0
  140. data/lib/longleaf/models/md_fields.rb +2 -1
  141. data/lib/longleaf/models/metadata_location.rb +47 -0
  142. data/lib/longleaf/models/metadata_record.rb +39 -15
  143. data/lib/longleaf/models/s3_storage_location.rb +133 -0
  144. data/lib/longleaf/models/service_definition.rb +7 -6
  145. data/lib/longleaf/models/service_fields.rb +7 -1
  146. data/lib/longleaf/models/service_record.rb +10 -6
  147. data/lib/longleaf/models/storage_location.rb +24 -19
  148. data/lib/longleaf/models/storage_types.rb +9 -0
  149. data/lib/longleaf/models/system_config_fields.rb +9 -0
  150. data/lib/longleaf/preservation_services/file_check_service.rb +58 -0
  151. data/lib/longleaf/preservation_services/fixity_check_service.rb +123 -0
  152. data/lib/longleaf/preservation_services/rsync_replication_service.rb +182 -0
  153. data/lib/longleaf/preservation_services/s3_replication_service.rb +143 -0
  154. data/lib/longleaf/services/application_config_deserializer.rb +81 -24
  155. data/lib/longleaf/services/application_config_manager.rb +20 -6
  156. data/lib/longleaf/services/application_config_validator.rb +19 -9
  157. data/lib/longleaf/services/configuration_validator.rb +67 -4
  158. data/lib/longleaf/services/filesystem_location_validator.rb +16 -0
  159. data/lib/longleaf/services/metadata_deserializer.rb +113 -42
  160. data/lib/longleaf/services/metadata_persistence_manager.rb +47 -0
  161. data/lib/longleaf/services/metadata_serializer.rb +138 -25
  162. data/lib/longleaf/services/metadata_validator.rb +76 -0
  163. data/lib/longleaf/services/s3_location_validator.rb +19 -0
  164. data/lib/longleaf/services/service_class_cache.rb +112 -0
  165. data/lib/longleaf/services/service_definition_manager.rb +10 -7
  166. data/lib/longleaf/services/service_definition_validator.rb +25 -18
  167. data/lib/longleaf/services/service_manager.rb +86 -11
  168. data/lib/longleaf/services/service_mapping_manager.rb +13 -12
  169. data/lib/longleaf/services/service_mapping_validator.rb +36 -26
  170. data/lib/longleaf/services/storage_location_manager.rb +76 -15
  171. data/lib/longleaf/services/storage_location_validator.rb +49 -35
  172. data/lib/longleaf/specs/config_builder.rb +47 -23
  173. data/lib/longleaf/specs/config_validator_helpers.rb +16 -0
  174. data/lib/longleaf/specs/custom_matchers.rb +9 -0
  175. data/lib/longleaf/specs/file_helpers.rb +61 -0
  176. data/lib/longleaf/specs/metadata_builder.rb +92 -0
  177. data/lib/longleaf/specs/system_config_builder.rb +27 -0
  178. data/lib/longleaf/version.rb +1 -1
  179. data/longleaf.gemspec +20 -7
  180. data/mkdocs.yml +21 -0
  181. metadata +306 -23
  182. data/.travis.yml +0 -4
  183. data/lib/longleaf/commands/abstract_command.rb +0 -37
  184. data/lib/longleaf/services/storage_path_validator.rb +0 -16
@@ -0,0 +1,76 @@
1
+ require 'pathname'
2
+ require 'longleaf/models/md_fields'
3
+ require 'longleaf/errors'
4
+ require_relative 'configuration_validator'
5
+
6
+ module Longleaf
7
+ # Validator for file metadata
8
+ class MetadataValidator < ConfigurationValidator
9
+ MDF ||= MDFields
10
+
11
+ # @param config [Hash] hash containing the application configuration
12
+ def initialize(config)
13
+ super(config)
14
+ end
15
+
16
+ protected
17
+ # Validates the provided metadata for a file to ensure that it is syntactically correct and field types
18
+ # are validate.
19
+ def validate
20
+ assert("Metadata must be a hash, but a #{@config.class} was provided", @config.class == Hash)
21
+ assert("Metadata must contain a '#{MDF::DATA}' key", @config.key?(MDF::DATA))
22
+ assert("Metadata must contain a '#{MDF::SERVICES}' key", @config.key?(MDF::SERVICES))
23
+
24
+ data = @config[MDF::DATA]
25
+ register_on_failure { validate_date_field(data, MDF::REGISTERED_TIMESTAMP) }
26
+ register_on_failure { validate_date_field(data, MDF::DEREGISTERED_TIMESTAMP, required: false) }
27
+ register_on_failure { validate_date_field(data, MDF::LAST_MODIFIED) }
28
+
29
+ register_on_failure { validate_positive_integer(data, MDF::FILE_SIZE) }
30
+
31
+ checksums = data[MDF::CHECKSUMS]
32
+ register_on_failure do
33
+ if !checksums.nil? && !checksums.is_a?(Hash)
34
+ fail("Field '#{MDF::CHECKSUMS}' must be a map of algorithms to digests, but was a #{checksums.class}")
35
+ end
36
+ end
37
+
38
+ # Ensure that any service timestamps present are valid dates
39
+ services = @config[MDF::SERVICES]
40
+ services.each do |service_name, service_rec|
41
+ register_on_failure { validate_date_field(service_rec, MDF::SERVICE_TIMESTAMP, required: false) }
42
+ end
43
+ end
44
+
45
+ def validate_date_field(section, field_key, required: true)
46
+ field_val = section[field_key]
47
+
48
+ if field_val
49
+ begin
50
+ Time.iso8601(section[field_key])
51
+ rescue ArgumentError
52
+ fail("Field '#{field_key}' must be a valid ISO8601 timestamp, but contained value '#{section[field_key]}'")
53
+ end
54
+ elsif required
55
+ fail("Metadata must contain a '#{field_key}' field")
56
+ end
57
+ end
58
+
59
+ def validate_positive_integer(section, field_key, required: true)
60
+ field_val = section[field_key]
61
+
62
+ if field_val
63
+ begin
64
+ val = field_val.is_a?(Integer) ? field_val : Integer(field_val, 10)
65
+ if val < 0
66
+ fail("Field '#{field_key}' must be a positive integer")
67
+ end
68
+ rescue ArgumentError => err
69
+ fail("Field '#{field_key}' must be a positive integer")
70
+ end
71
+ elsif required
72
+ fail("Metadata must contain a '#{field_key}' field")
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,19 @@
1
+ require 'pathname'
2
+ require 'longleaf/errors'
3
+ require 'longleaf/helpers/s3_uri_helper'
4
+
5
+ module Longleaf
6
+ # Validates the configuration of a s3 based location
7
+ class S3LocationValidator
8
+ def self.validate(p_validator, name, path_prop, section_name, path)
9
+ base_msg = "Storage location '#{name}' specifies invalid #{section_name} '#{path_prop}' property: "
10
+ p_validator.assert(base_msg + 'Path must not be empty', !path.nil? && !path.to_s.strip.empty?)
11
+ begin
12
+ bucket_name = S3UriHelper.extract_bucket(path)
13
+ p_validator.assert(base_msg + 'Path must specify a bucket', !bucket_name.nil?)
14
+ rescue ArgumentError => e
15
+ p_validator.fail(base_msg + e.message)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,112 @@
1
+ require 'pathname'
2
+
3
+ module Longleaf
4
+ # Cache for loading and retrieving preservation service classes
5
+ class ServiceClassCache
6
+ STD_PRESERVATION_SERVICE_PATH = 'longleaf/preservation_services/'
7
+
8
+ def initialize(app_manager)
9
+ @app_manager = app_manager
10
+ # Cache storing per service definition instances of service classes
11
+ @service_instance_cache = Hash.new
12
+ # Cache storing per script path class of service
13
+ @class_cache = Hash.new
14
+ end
15
+
16
+ # Returns an instance of the preversation service defined for the provided service definition,
17
+ # based on the work_script and work_class properties provided.
18
+ #
19
+ # @param service_def [ServiceDefinition] definition of service to instantiate
20
+ # @return [PreservationService] Instance of the preservation service class for the definition.
21
+ def service_instance(service_def)
22
+ service_name = service_def.name
23
+ # Return the cached instance of the service
24
+ if @service_instance_cache.key?(service_name)
25
+ return @service_instance_cache[service_name]
26
+ end
27
+
28
+ clazz = service_class(service_def)
29
+ # Cache and return the class instance
30
+ @service_instance_cache[service_name] = clazz.new(service_def, @app_manager)
31
+ end
32
+
33
+ # Load and return the PreservationService class assigned to the provided service definition,
34
+ # based on the work_script and work_class properties provided.
35
+ #
36
+ # @param service_def [ServiceDefinition] definition of service to retrieve class for
37
+ # @return [Class] class of work_script
38
+ def service_class(service_def)
39
+ service_name = service_def.name
40
+ work_script = service_def.work_script
41
+
42
+ if work_script.include?('/')
43
+ expanded_path = Pathname.new(work_script).expand_path.to_s
44
+ if !from_permitted_path?(expanded_path)
45
+ raise ConfigurationError.new("Unable to load work_script for service #{service_name}, #{work_script} is not in a known library path.")
46
+ end
47
+
48
+ last_slash_index = work_script.rindex('/')
49
+ script_path = work_script[0..last_slash_index]
50
+ script_name = work_script[(last_slash_index + 1)..-1]
51
+ else
52
+ script_path = STD_PRESERVATION_SERVICE_PATH
53
+ script_name = work_script
54
+ end
55
+
56
+ # Strip off the extension
57
+ script_name.sub!('.rb', '')
58
+
59
+ require_path = File.join(script_path, script_name)
60
+ # Return the cached Class if this path has been encountered before
61
+ if @class_cache.key?(require_path)
62
+ return @class_cache[require_path]
63
+ end
64
+
65
+ # Load the script
66
+ begin
67
+ require require_path
68
+ rescue LoadError
69
+ raise ConfigurationError.new("Failed to load work_script '#{script_name}' for service #{service_name}")
70
+ end
71
+
72
+ # Generate the class name, either configured or from file naming convention if possible
73
+ if service_def.work_class
74
+ class_name = service_def.work_class
75
+ else
76
+ class_name = script_name.split('_').map(&:capitalize).join
77
+ # Assume the longleaf module for classes in the standard path
78
+ class_name = 'Longleaf::' + class_name if script_path == STD_PRESERVATION_SERVICE_PATH
79
+ end
80
+
81
+ begin
82
+ class_constant = constantize(class_name)
83
+ # cache the class for this work_script and return it
84
+ @class_cache[require_path] = class_constant
85
+ rescue NameError
86
+ raise ConfigurationError.new("Failed to load work_script '#{script_name}' for service #{service_name}, class name #{class_name} was not found.")
87
+ end
88
+ end
89
+
90
+ private
91
+ # Borrowed from sidekiq implementation
92
+ def constantize(str)
93
+ names = str.split('::')
94
+ names.shift if names.empty? || names.first.empty?
95
+
96
+ names.inject(Object) do |constant, name|
97
+ # the false flag limits search for name to under the constant namespace
98
+ # which mimics Rails' behaviour
99
+ constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
100
+ end
101
+ end
102
+
103
+ def from_permitted_path?(script_path)
104
+ $LOAD_PATH.each do |lib_path|
105
+ if script_path.start_with?(lib_path)
106
+ return true
107
+ end
108
+ end
109
+ false
110
+ end
111
+ end
112
+ end
@@ -1,36 +1,39 @@
1
1
  require_relative '../models/app_fields'
2
2
  require_relative '../models/service_definition'
3
3
 
4
- # Manager which loads and provides access to Longleaf::ServiceDefinition objects
5
4
  module Longleaf
5
+ # Manager which loads and provides access to Longleaf::ServiceDefinition objects
6
6
  class ServiceDefinitionManager
7
7
  SF ||= Longleaf::ServiceFields
8
8
  AF ||= Longleaf::AppFields
9
-
9
+
10
+ # Hash containing the set of configured services, represented as {ServiceDefinition} objects
10
11
  attr_reader :services
11
-
12
+
13
+ # @param config [Hash] hash representation of the application configuration
12
14
  def initialize(config)
13
15
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
14
16
 
15
17
  services_config = config[AF::SERVICES]
16
18
  raise ArgumentError.new("Services configuration must be provided") if services_config.nil?
17
-
19
+
18
20
  @services = Hash.new
19
21
  config[AF::SERVICES].each do |name, properties|
20
22
  work_script = properties.delete(SF::WORK_SCRIPT)
23
+ work_class = properties.delete(SF::WORK_CLASS)
21
24
  frequency = properties.delete(SF::FREQUENCY)
22
25
  delay = properties.delete(SF::DELAY)
23
26
  service = Longleaf::ServiceDefinition.new(
24
27
  name: name,
25
28
  work_script: work_script,
29
+ work_class: work_class,
26
30
  frequency: frequency,
27
31
  delay: delay,
28
32
  properties: properties)
29
-
33
+
30
34
  @services[name] = service
31
35
  end
32
36
  @services.freeze
33
37
  end
34
-
35
38
  end
36
- end
39
+ end
@@ -1,32 +1,39 @@
1
1
  require 'pathname'
2
- require_relative '../models/service_fields'
3
- require_relative '../models/app_fields'
4
- require_relative '../errors'
2
+ require 'longleaf/models/service_fields'
3
+ require 'longleaf/models/app_fields'
4
+ require 'longleaf/errors'
5
5
  require_relative 'configuration_validator'
6
6
 
7
- # Validates application configuration of service definitions
8
7
  module Longleaf
8
+ # Validates application configuration of service definitions
9
9
  class ServiceDefinitionValidator < ConfigurationValidator
10
10
  SF ||= Longleaf::ServiceFields
11
11
  AF ||= Longleaf::AppFields
12
-
13
- # Validates configuration to ensure that it is syntactically correct and does not violate
12
+
13
+ # @param config [Hash] hash containing the application configuration
14
+ def initialize(config)
15
+ super(config)
16
+ end
17
+
18
+ protected
19
+ # Validates configuration to ensure that it is syntactically correct and does not violate
14
20
  # schema requirements.
15
21
  # @param config [Hash] hash containing the application configuration
16
- def self.validate_config(config)
17
- assert("Configuration must be a hash, but a #{config.class} was provided", config.class == Hash)
18
- assert("Configuration must contain a root '#{AF::SERVICES}' key", config.key?(AF::SERVICES))
19
- services = config[AF::SERVICES]
22
+ def validate
23
+ assert("Configuration must be a hash, but a #{@config.class} was provided", @config.class == Hash)
24
+ assert("Configuration must contain a root '#{AF::SERVICES}' key", @config.key?(AF::SERVICES))
25
+ services = @config[AF::SERVICES]
20
26
  assert("'#{AF::SERVICES}' must be a hash of services", services.class == Hash)
21
-
22
- existing_paths = Array.new
27
+
23
28
  services.each do |name, properties|
24
- assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
25
- assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
26
-
27
- work_script = properties[SF::WORK_SCRIPT]
28
- assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
29
+ register_on_failure do
30
+ assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
31
+ assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
32
+
33
+ work_script = properties[SF::WORK_SCRIPT]
34
+ assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
35
+ end
29
36
  end
30
37
  end
31
38
  end
32
- end
39
+ end
@@ -1,21 +1,96 @@
1
- # Manager which provides preservation service definitions based on their mappings
1
+ require 'longleaf/helpers/service_date_helper'
2
+ require 'longleaf/services/service_class_cache'
3
+
2
4
  module Longleaf
5
+ # Manager which provides preservation service definitions based on their mappings
3
6
  class ServiceManager
4
-
5
- def initialize(definition_manager:, mapping_manager:)
7
+ attr_reader :definition_manager
8
+ attr_reader :mapping_manager
9
+
10
+ # @param definition_manager [ServiceDefinitionManager] the service definition manager
11
+ # @param mapping_manager [ServiceMappingManager] the mapping of services to locations
12
+ # @param app_manager [ApplicationConfigManager] manager for storage locations
13
+ def initialize(definition_manager:, mapping_manager:, app_manager:)
6
14
  raise ArgumentError.new('Service definition manager required') if definition_manager.nil?
7
15
  raise ArgumentError.new('Service mappings manager required') if mapping_manager.nil?
16
+ raise ArgumentError.new('Storage location manager required') if app_manager.nil?
8
17
  @definition_manager = definition_manager
9
18
  @mapping_manager = mapping_manager
19
+ @app_manager = app_manager
20
+ @service_class_cache = ServiceClassCache.new(app_manager)
10
21
  end
11
-
12
- # Gets a list of ServiceDefinition objects which match the given criteria
13
- # @param location [String] name of the location to lookup
14
- # @return [Array] a list of ServiceDefinition objects associated with the location,
15
- # or an empty list if no services match the criteria
16
- def list_service_definitions(location: nil)
22
+
23
+ # Return a service instance instance for provided service name.
24
+ # @param service_name [String] name of the service
25
+ # @return Preservation service class for the provided name
26
+ # @raise ArgumentError if service_name does not reference an existing service
27
+ def service(service_name)
28
+ raise ArgumentError.new('Service name is required') if service_name.nil? || service_name.empty?
29
+ raise ArgumentError.new("No service with name #{service_name}") unless @definition_manager.services.key?(service_name)
30
+ definition = @definition_manager.services[service_name]
31
+ @service_class_cache.service_instance(definition)
32
+ end
33
+
34
+ # List the names of services which are applicable to the given criteria
35
+ # @param location [String] name of the locations to lookup
36
+ # @param event [String] name of the preservation event taking place
37
+ # @return [Array] a list of service names which match the provided criteria
38
+ def list_services(location: nil, event: nil)
17
39
  service_names = @mapping_manager.list_services(location)
18
- service_names.collect { |name| @definition_manager.services[name] }
40
+ if !event.nil?
41
+ # Filter service names down by event
42
+ service_names.select { |name| applicable_for_event?(name, event) }
43
+ else
44
+ service_names
45
+ end
46
+ end
47
+
48
+ # List definitions for services which are applicable to the given criteria
49
+ # @param location [String] name of the locations to lookup
50
+ # @param event [String] name of the preservation event taking place
51
+ # @return [Array] List of service definitions which match the provided criteria
52
+ def list_service_definitions(location: nil, event: nil)
53
+ names = list_services(location: location, event: event)
54
+ names.map { |name| @definition_manager.services[name] }
55
+ end
56
+
57
+ # Determines if a service is applicable for a specific preservation event
58
+ # @param service_name [String] name of the service being evaluated
59
+ # @param event [String] name of the event to check against
60
+ # @return [Boolean] true if the service is applicable for the event
61
+ def applicable_for_event?(service_name, event)
62
+ service(service_name).is_applicable?(event)
63
+ end
64
+
65
+ # Determine if a service should run for a particular file based on the service's definition and
66
+ # the file's service related metadata.
67
+ # @param service_name [String] name of the service being evaluated
68
+ # @param md_rec [MetadataRecord] metadata record for the file being evaluated
69
+ # @return [Boolean] true if the service should be run.
70
+ def service_needed?(service_name, md_rec)
71
+ service_rec = md_rec.service(service_name)
72
+ return true if !service_rec.nil? && service_rec.run_needed
73
+
74
+ definition = @definition_manager.services[service_name]
75
+
76
+ next_run = ServiceDateHelper.next_run_needed(md_rec, definition)
77
+
78
+ return false if next_run.nil?
79
+
80
+ # If next run timestamp has passed then service is needed
81
+ now = ServiceDateHelper.formatted_timestamp
82
+ now >= next_run
83
+ end
84
+
85
+ # Perform the specified service on the file record, in the context of the specified event
86
+ # @param service_name [String] name of the service
87
+ # @param file_rec [FileRecord] file record to perform service upon
88
+ # @param event [String] name of the event service is being performed within.
89
+ def perform_service(service_name, file_rec, event)
90
+ definition = @definition_manager.services[service_name]
91
+
92
+ service = @service_class_cache.service_instance(definition)
93
+ service.perform(file_rec, event)
19
94
  end
20
95
  end
21
- end
96
+ end
@@ -1,29 +1,30 @@
1
- require_relative '../models/app_fields'
2
- require_relative '../models/service_definition'
1
+ require 'longleaf/models/app_fields'
2
+ require 'longleaf/models/service_definition'
3
3
 
4
- # Manager which loads and provides access to location to service mappings
5
4
  module Longleaf
5
+ # Manager which loads and provides access to location to service mappings
6
6
  class ServiceMappingManager
7
7
  AF ||= Longleaf::AppFields
8
-
8
+
9
+ # @param config [Hash] has representation of the application configuration
9
10
  def initialize(config)
10
11
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
11
12
 
12
13
  mappings_config = config[AF::SERVICE_MAPPINGS]
13
14
  raise ArgumentError.new("Service mappings configuration must be provided") if mappings_config.nil?
14
-
15
+
15
16
  @loc_to_services = Hash.new
16
-
17
+
17
18
  mappings_config.each do |mapping|
18
19
  locations = mapping[AF::LOCATIONS]
19
20
  services = mapping[AF::SERVICES]
20
-
21
+
21
22
  locations = [locations] if locations.is_a?(String)
22
23
  services = [services] if services.is_a?(String)
23
-
24
+
24
25
  locations.each do |loc_name|
25
26
  @loc_to_services[loc_name] = Array.new unless @loc_to_services.key?(loc_name)
26
-
27
+
27
28
  service_set = @loc_to_services[loc_name]
28
29
  if services.is_a?(String)
29
30
  service_set.push(services)
@@ -32,11 +33,11 @@ module Longleaf
32
33
  end
33
34
  end
34
35
  end
35
-
36
+
36
37
  @loc_to_services.each { |loc, services| services.uniq! }
37
38
  @loc_to_services.freeze
38
39
  end
39
-
40
+
40
41
  # Gets a list of service names associated with the given location
41
42
  # @param loc_name [String] name of the location to lookup
42
43
  # @return [Array] a list of service names associated with the location
@@ -44,4 +45,4 @@ module Longleaf
44
45
  @loc_to_services[loc_name] || []
45
46
  end
46
47
  end
47
- end
48
+ end