diff --git a/README.md b/README.md index ba249873..16405cb6 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,10 @@ uv = createUV('#uv', { ## Configuration to enable IiifPrint features **NOTE: WorkTypes and models are used synonymously here.** +### Persistence Layer Adapter + +We created IiifPrint with an assumption of ActiveFedora. However, as Hyrax now supports Valkyrie, we need an alternate approach. We introduced `IiifPrint::Configuration#persistence_layer` as a configuration option. By default it will use `ActiveFedora` methods; but you can switch adapters to use Valkyrie instead. (See `IiifPrint::PersistentLayer` for more details). + ### IIIF URL configuration If you set EXTERNAL_IIIF_URL in your environment, then IiifPrint will use that URL as the root for your IIIF URLs. It will also switch from using the file set ID to using the SHA1 of the file as the identifier. This enables using serverless_iiif or Cantaloupe (refered to as the service) by pointing the service to the same S3 bucket that FCREPO writes the uploaded files to. By setting it up that way you do not need the service to connect to FCREPO or Hyrax at all, both natively support connecting to an S3 bucket to get their data. diff --git a/app/models/concerns/iiif_print/solr/document.rb b/app/models/concerns/iiif_print/solr/document.rb index 84e7e996..1e0806a6 100644 --- a/app/models/concerns/iiif_print/solr/document.rb +++ b/app/models/concerns/iiif_print/solr/document.rb @@ -39,7 +39,7 @@ def digest_sha1 def method_missing(method_name, *args, &block) super unless iiif_print_solr_field_names.include? method_name.to_s - self[::ActiveFedora.index_field_mapper.solr_name(method_name.to_s)] + self[IiifPrint.solr_name(method_name.to_s)] end def respond_to_missing?(method_name, include_private = false) diff --git a/app/services/iiif_print/manifest_builder_service_behavior.rb b/app/services/iiif_print/manifest_builder_service_behavior.rb index d198946a..9c49ff3d 100644 --- a/app/services/iiif_print/manifest_builder_service_behavior.rb +++ b/app/services/iiif_print/manifest_builder_service_behavior.rb @@ -142,7 +142,7 @@ def get_solr_hits(ids) results = [] ids.each_slice(SOLR_QUERY_PAGE_SIZE) do |paged_ids| query = "id:(#{paged_ids.join(' OR ')})" - results += ActiveFedora::SolrService.query( + results += IiifPrint.solr_query( query, { fq: "-has_model_ssim:FileSet", rows: paged_ids.size, method: :post } ) diff --git a/lib/iiif_print.rb b/lib/iiif_print.rb index d89e426d..c5499f86 100644 --- a/lib/iiif_print.rb +++ b/lib/iiif_print.rb @@ -44,37 +44,8 @@ def self.config(&block) end class << self - delegate :skip_splitting_pdf_files_that_end_with_these_texts, to: :config - end - - ## - # Return the immediate parent of the given :file_set. - # - # @param file_set [FileSet] - # @return [#work?, Hydra::PCDM::Work] - # @return [NilClass] when no parent is found. - def self.parent_for(file_set) - # fallback to Fedora-stored relationships if work's aggregation of - # file set is not indexed in Solr - file_set.parent || file_set.member_of.find(&:work?) - end - - ## - # Return the parent's parent of the given :file_set. - # - # @param file_set [FileSet] - # @return [#work?, Hydra::PCDM::Work] - # @return [NilClass] when no grand parent is found. - def self.grandparent_for(file_set) - parent_of_file_set = parent_for(file_set) - # HACK: This is an assumption about the file_set structure, namely that an image page split from - # a PDF is part of a file set that is a child of a work that is a child of a single work. That - # is, it only has one grand parent. Which is a reasonable assumption for IIIF Print but is not - # valid when extended beyond IIIF Print. That is GenericWork does not have a parent method but - # does have a parents method. - parent_of_file_set.try(:parent_works).try(:first) || - parent_of_file_set.try(:parents).try(:first) || - parent_of_file_set&.member_of&.find(&:work?) + delegate :skip_splitting_pdf_files_that_end_with_these_texts, :persistence_adapter, to: :config + delegate :parent_for, :grandparent_for, :solr_construct_query, :solr_query, :solr_name, :clean_for_tests!, to: :persistence_adapter end DEFAULT_MODEL_CONFIGURATION = { diff --git a/lib/iiif_print/catalog_search_builder.rb b/lib/iiif_print/catalog_search_builder.rb index e230881a..1afd661b 100644 --- a/lib/iiif_print/catalog_search_builder.rb +++ b/lib/iiif_print/catalog_search_builder.rb @@ -25,9 +25,9 @@ class CatalogSearchBuilder < Hyrax::CatalogSearchBuilder # rubocop:enable Naming/PredicateName def show_parents_only(solr_parameters) query = if blacklight_params["include_child_works"] == 'true' - ActiveFedora::SolrQueryBuilder.construct_query(is_child_bsi: 'true') + IiifPrint.solr_construct_query(is_child_bsi: 'true') else - ActiveFedora::SolrQueryBuilder.construct_query(is_child_bsi: nil) + IiifPrint.solr_construct_query(is_child_bsi: nil) end solr_parameters[:fq] += [query] end diff --git a/lib/iiif_print/configuration.rb b/lib/iiif_print/configuration.rb index 03bf5b55..90ed3125 100644 --- a/lib/iiif_print/configuration.rb +++ b/lib/iiif_print/configuration.rb @@ -3,6 +3,22 @@ module IiifPrint class Configuration attr_writer :after_create_fileset_handler + attr_writer :persistence_adapter + def persistence_adapter + @persistent_adapter || default_persistence_adapter + end + + def default_persistence_adapter + # There's probably some configuration of Hyrax we could use to better refine this; but it's + # likely a reasonable guess. The main goal is to not break existing implementations and + # maintain an upgrade path. + if Gem::Version.new(Hyrax::VERSION) >= Gem::Version.new('6.0.0') + IiifPrint::PersistenceLayer::ValkyrieAdapter + else + IiifPrint::PersistenceLayer::ActiveFedoraAdapter + end + end + # @param file_set [FileSet] # @param user [User] def handle_after_create_fileset(file_set, user) diff --git a/lib/iiif_print/engine.rb b/lib/iiif_print/engine.rb index b314e35f..82131d65 100644 --- a/lib/iiif_print/engine.rb +++ b/lib/iiif_print/engine.rb @@ -11,6 +11,12 @@ module IiifPrint class Engine < ::Rails::Engine isolate_namespace IiifPrint + initializer 'requires' do + require 'iiif_print/persistence_layer' + require 'iiif_print/persistence_layer/active_fedora_adapter' if defined?(ActiveFedora) + require 'iiif_print/persistence_layer/valkyrie_adapter' if defined?(Valkyrie) + end + # rubocop:disable Metrics/BlockLength config.to_prepare do require "iiif_print/jobs/create_relationships_job" diff --git a/lib/iiif_print/homepage_search_builder.rb b/lib/iiif_print/homepage_search_builder.rb index b55506ef..255adcc1 100644 --- a/lib/iiif_print/homepage_search_builder.rb +++ b/lib/iiif_print/homepage_search_builder.rb @@ -7,9 +7,9 @@ class HomepageSearchBuilder < Hyrax::HomepageSearchBuilder def show_parents_only(solr_parameters) query = if blacklight_params["include_child_works"] == 'true' - ActiveFedora::SolrQueryBuilder.construct_query(is_child_bsi: 'true') + IiifPrint.solr_construct_query(is_child_bsi: 'true') else - ActiveFedora::SolrQueryBuilder.construct_query(is_child_bsi: nil) + IiifPrint.solr_construct_query(is_child_bsi: nil) end solr_parameters[:fq] += [query] end diff --git a/lib/iiif_print/persistence_layer.rb b/lib/iiif_print/persistence_layer.rb new file mode 100644 index 00000000..15b8a00f --- /dev/null +++ b/lib/iiif_print/persistence_layer.rb @@ -0,0 +1,40 @@ +module IiifPrint + ## + # The PersistenceLayer module provides the namespace for other adapters: + # + # - {IiifPrint::PersistenceLayer::ActiveFedoraAdapter} + # - {IiifPrint::PersistenceLayer::ValkyrieAdapter} + # + # And the defining interface in the {IiifPrint::PersistenceLayer::AbstractAdapter} + module PersistenceLayer + # @abstract + class AbstractAdapter + ## + # @abstract + def self.parent_for(*); end + + ## + # @abstract + def self.grandparent_for(*); end + + ## + # @abstract + def self.solr_field_query(*); end + + ## + # @abstract + def self.clean_for_tests! + return false unless Rails.env.test? + yield + end + + ## + # @abstract + def self.solr_query(*args); end + + ## + # @abstract + def self.solr_name(*args); end + end + end +end diff --git a/lib/iiif_print/persistence_layer/active_fedora_adapter.rb b/lib/iiif_print/persistence_layer/active_fedora_adapter.rb new file mode 100644 index 00000000..e6299fb8 --- /dev/null +++ b/lib/iiif_print/persistence_layer/active_fedora_adapter.rb @@ -0,0 +1,65 @@ +module IiifPrint + module PersistenceLayer + class ActiveFedoraAdapter < AbstractAdapter + ## + # Return the immediate parent of the given :file_set. + # + # @param file_set [FileSet] + # @return [#work?, Hydra::PCDM::Work] + # @return [NilClass] when no parent is found. + def self.parent_for(file_set) + # fallback to Fedora-stored relationships if work's aggregation of + # file set is not indexed in Solr + file_set.parent || file_set.member_of.find(&:work?) + end + + ## + # Return the parent's parent of the given :file_set. + # + # @param file_set [FileSet] + # @return [#work?, Hydra::PCDM::Work] + # @return [NilClass] when no grand parent is found. + def self.grandparent_for(file_set) + parent_of_file_set = parent_for(file_set) + # HACK: This is an assumption about the file_set structure, namely that an image page split from + # a PDF is part of a file set that is a child of a work that is a child of a single work. That + # is, it only has one grand parent. Which is a reasonable assumption for IIIF Print but is not + # valid when extended beyond IIIF Print. That is GenericWork does not have a parent method but + # does have a parents method. + parent_of_file_set.try(:parent_works).try(:first) || + parent_of_file_set.try(:parents).try(:first) || + parent_of_file_set&.member_of&.find(&:work?) + end + + def self.solr_construct_query(*args) + if defined?(Hyrax::SolrQueryBuilderService) + Hyrax::SolrQueryBuilderService.construct_query(*args) + else + ActiveFedora::SolrQueryBuilderService.construct_query(*args) + end + end + + def self.clean_for_tests! + super do + ActiveFedora::Cleaner.clean! + end + end + + def self.solr_query(*args) + if defined?(Hyrax::SolrService) + Hyrax::SolrService.query(*args) + else + ActiveFedora::SolrService.query(*args) + end + end + + def self.solr_name(field_name) + if defined?(Hyrax) && Hyrax.config.respond_to?(:index_field_mapper) + Hyrax.config.index_field_mapper.solr_name(field_name.to_s) + else + ::ActiveFedora.index_field_mapper.solr_name(field_name.to_s) + end + end + end + end +end diff --git a/lib/iiif_print/persistence_layer/valkyrie_adapter.rb b/lib/iiif_print/persistence_layer/valkyrie_adapter.rb new file mode 100644 index 00000000..8617c404 --- /dev/null +++ b/lib/iiif_print/persistence_layer/valkyrie_adapter.rb @@ -0,0 +1,45 @@ +module IiifPrint + module PersistenceLayer + class ValkyrieAdapter < AbstractAdapter + ## + # Return the immediate parent of the given :file_set. + # + # @param file_set [FileSet] + # @return [#work?, Hydra::PCDM::Work] + # @return [NilClass] when no parent is found. + def self.parent_for(file_set) + Hyrax.index_adapter.find_parents(resource: file_set).first + end + + ## + # Return the parent's parent of the given :file_set. + # + # @param file_set [FileSet] + # @return [#work?, Hydra::PCDM::Work] + # @return [NilClass] when no grand parent is found. + def self.grandparent_for(file_set) + parent = Hyrax.index_adapter.find_parents(resource: file_set).first + return nil unless parent + Hyrax.index_adapter.find_parents(resource: parent).first + end + + def self.solr_construct_query(*args) + Hyrax::SolrQueryBuilderService.construct_query(*args) + end + + def self.clean_for_tests! + # For Fedora backed repositories, we'll want to consider some cleaning mechanism. For + # database backed repositories, we can rely on the database_cleaner gem. + raise NotImplementedError + end + + def self.solr_query(*args) + Hyrax::SolrService.query(*args) + end + + def self.solr_name(field_name) + Hyrax.config.index_field_mapper.solr_name(field_name.to_s) + end + end + end +end diff --git a/spec/iiif_print/configuration_spec.rb b/spec/iiif_print/configuration_spec.rb index 0ae66f03..4d8f3b54 100644 --- a/spec/iiif_print/configuration_spec.rb +++ b/spec/iiif_print/configuration_spec.rb @@ -3,6 +3,12 @@ RSpec.describe IiifPrint::Configuration do let(:config) { described_class.new } + describe '#persistence_adapter' do + subject { config.persistence_adapter } + + it { is_expected.to eq(IiifPrint::PersistenceLayer::ActiveFedoraAdapter) } + end + describe '#ancestory_identifier_function' do subject(:function) { config.ancestory_identifier_function } it "is expected to be a lambda with an arity of one" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 94023e1b..ad534478 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,7 +50,7 @@ ::Noid::Rails.config.minter_class = minter_class Hyrax.config.noid_minter_class = minter_class - ActiveFedora::Cleaner.clean! + IiifPrint.clean_for_tests! DatabaseCleaner.clean_with(:truncation) begin