app/services/hyrax/admin_set_create_service.rb
# frozen_string_literal: true
# Hyrax 5.0.1 override:
# - change DEFAULT_ID to 'admin_set_default' to resolve encoded slash issues in Fedora 6.5.0
# - resolve rubocop warnings
module Hyrax
# Responsible for creating a Hyrax::AdministrativeSet and its corresponding data.
#
# * An associated permission template
# * Available workflows
# * An active workflow
#
# @see Hyrax::AdministrativeSet
# @see Hyrax::PermissionTemplate
# @see Sipity::Workflow
class AdminSetCreateService # rubocop:disable Metrics/ClassLength
DEFAULT_ID = 'admin_set_default'
DEFAULT_TITLE = ['Default Admin Set'].freeze
class_attribute :permissions_create_service, :default_admin_set_persister
self.permissions_create_service = Hyrax::Collections::PermissionsCreateService
self.default_admin_set_persister = Hyrax::DefaultAdministrativeSet
class << self
# @api public
# Finds the default AdministrativeSet if it exists; otherwise, creates it and corresponding data
# @return [Hyrax::AdministrativeSet] The default admin set.
# @see Hyrax::AdministrativeSet
# @raise [RuntimeError] if admin set cannot be persisted
def find_or_create_default_admin_set
find_default_admin_set || create_default_admin_set!
end
# @api public
# @param id [#to_s] id of the admin set to check
# @return [Boolean] true if the id is for the default admin set; otherwise, false
def default_admin_set?(id:)
return false if id.blank?
id.to_s == default_admin_set_id
end
# @api public
# Creates a non-default Hyrax::AdministrativeSet and corresponding data
# @param admin_set [Hyrax::AdministrativeSet | AdminSet] the admin set to operate on
# @param creating_user [User] the user who created the admin set
# @return [TrueClass, FalseClass] true if it was successful
# @see Hyrax::AdministrativeSet
# @raise [RuntimeError] if you attempt to create a default admin set via this mechanism
# @deprecated
def call(admin_set:, creating_user:, **kwargs)
Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
"Warning: This method may hide runtime errors. " \
"Instead, use 'Hyrax::AdminSetCreateService.call!'. ")
call!(admin_set: admin_set_resource(admin_set), creating_user:, **kwargs).present?
rescue RuntimeError => err
raise err if default_admin_set?(id: admin_set.id)
false
end
# @api public
# Creates a non-default Hyrax::AdministrativeSet and corresponding data
# @param admin_set [Hyrax::AdministrativeSet] the admin set to operate on
# @param creating_user [User] the user who created the admin set
# @return [Hyrax::AdministrativeSet] The fully created admin set.
# @see Hyrax::AdministrativeSet
# @raise [RuntimeError] if you attempt to create a default admin set via this mechanism
# @raise [RuntimeError] if admin set cannot be persisted
def call!(admin_set:, creating_user:, **kwargs)
raise "Use .find_or_create_default_admin_set to create a default admin set" if default_admin_set?(id: admin_set.id)
new(admin_set:, creating_user:, **kwargs).create!
end
private
# TODO: Parameters admin_set_id and title are defined to support .create_default_admin_set
# which is deprecated. When it is removed, the parameters will no longer be required.
def create_default_admin_set!(admin_set_id: DEFAULT_ID, title: DEFAULT_TITLE)
admin_set = create_admin_set(suggested_id: admin_set_id, title:)
admin_set = new(admin_set:, creating_user: nil, default_admin_set: true).create!
default_admin_set_persister.update(default_admin_set_id: admin_set.id) if save_default?
admin_set
end
def save_default?
default_admin_set_persister.save_supported?
end
# Create an instance of `Hyrax::AdministrativeSet` with the suggested_id if supported.
# @return [Hyrax::AdministrativeSet] the new admin set
def create_admin_set(suggested_id:, title:)
# Leverage the configured admin class, if it is a Valkyrie resource, otherwise fallback.
# Until we have fully moved to Valkyrie, we will need this logic. Once past, we can
# use `Hyrax.config.admin_set_class`
klass = Hyrax.config.admin_set_class < Valkyrie::Resource ? Hyrax.config.admin_set_class : Hyrax::AdministrativeSet
if suggested_id.blank? || Hyrax.config.disable_wings
# allow persister to assign id
klass.new(title: Array.wrap(title))
else
# use suggested_id
klass.new(id: suggested_id, title: Array.wrap(title))
end
end
# Find default AdministrativeSet using saved id
# @return [Hyrax::AdministrativeSet] the default admin set; nil if id not saved
# @raise [RuntimeError] if an admin set with the saved id doesn't exist
def find_default_admin_set
id = default_admin_set_id
return if id.blank?
Hyrax.query_service.find_by(id:)
rescue Valkyrie::Persistence::ObjectNotFoundError
# The default ID is DEFAULT_ID when saving is not supported. It is ok
# for this default id to be known but not found. The admin set will be
# created with DEFAULT_ID by find_or_create_default_admin_set.
return unless save_default?
# id is saved in the default_admin_set_persister's table but doesn't exist
# NOTE: This is a corrupt state and shouldn't happen. Manual intervention
# is required to determine the correct value for the default admin
# set id. The saved id either needs to be updated to the correct
# value or deleted to allow a new default admin set to be found
# (i.e. an admin set with id DEFAULT_ID) or generated.
raise "Corrupt default admin set. Persisted admin set with saved default_admin_set_id doesn't exist."
end
# Find default AdministrativeSet using DEFAULT_ID.
# @note Use of hardcoded ID is being deprecated as some Valkyrie adapters
# do not support hardcoded IDs (e.g. postgres)
# @return [Hyrax::AdministrativeSet] the default admin set; nil if not found
def find_unsaved_default_admin_set
admin_set = Hyrax.query_service.find_by(id: DEFAULT_ID)
default_admin_set_persister.update(default_admin_set_id: DEFAULT_ID) if save_default?
admin_set
rescue Valkyrie::Persistence::ObjectNotFoundError
# a default admin set hasn't been created yet
end
# @return [String | nil] the default admin set id; returns nil if not set
# @note For general use, it is better to use `Hyrax.config.default_admin_set_id`.
def default_admin_set_id
return DEFAULT_ID unless save_default?
id = default_admin_set_persister.first&.default_admin_set_id
id = find_unsaved_default_admin_set&.id&.to_s if id.blank?
id
end
def admin_set_resource(admin_set)
case admin_set
when Valkyrie::Resource
admin_set
else
admin_set.valkyrie_resource
end
end
end
# @param admin_set [Hyrax::AdministrativeSet | AdminSet] the admin set to operate on
# @param creating_user [User] the user who created the admin set (if any).
# @param workflow_importer [#call] imports the workflow
def initialize(admin_set:, creating_user:, workflow_importer: default_workflow_importer, default_admin_set: false)
@admin_set = Hyrax::AdminSetCreateService.send(:admin_set_resource, admin_set)
@creating_user = creating_user
@workflow_importer = workflow_importer
@default_admin_set = default_admin_set
end
attr_reader :creating_user, :admin_set, :workflow_importer
# Creates an admin set, setting the creator and the default access controls.
# @return [TrueClass, FalseClass] true if it was successful
# @deprecated
def create
Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
"Warning: This method may hide runtime errors. " \
"Instead, use 'Hyrax::AdminSetCreateService.create!'. ")
create!.persisted?
rescue RuntimeError => _err
false
end
# Creates an admin set, setting the creator and the default access controls.
# @return [Hyrax::AdministrativeSet] The fully created admin set.
# @raise [RuntimeError] if admin set cannot be persisted
def create!
admin_set.creator = [creating_user.user_key] if creating_user
updated_admin_set = Hyrax.persister.save(resource: admin_set).tap do |result|
if result
ActiveRecord::Base.transaction do
permission_template = PermissionTemplate.find_by(source_id: result.id.to_s) ||
permissions_create_service.create_default(collection: result,
creating_user:)
workflow = create_workflows_for(permission_template:)
create_default_access_for(permission_template:, workflow:) if default_admin_set?
end
end
end
Hyrax.publisher.publish('collection.metadata.updated', collection: updated_admin_set, user: creating_user)
updated_admin_set
end
private
def default_admin_set?
@default_admin_set
end
def admin_group_name
::Ability.admin_group_name
end
def create_workflows_for(permission_template:)
workflow_importer.call(permission_template:)
grant_all_workflow_roles_to_creating_user_and_admins!(permission_template:)
Sipity::Workflow.activate!(permission_template:, workflow_name: Hyrax.config.default_active_workflow_name)
end
# Force creation of registered MANAGING role if it doesn't exist
def register_managing_role!
Sipity::Role[Hyrax::RoleRegistry::MANAGING]
end
def grant_all_workflow_roles_to_creating_user_and_admins!(permission_template:)
# This code must be invoked before calling `Sipity::Role.all` or the managing role won't be there
register_managing_role!
# Grant all workflow roles to the creating_user and the admin group
permission_template.available_workflows.each do |workflow|
Sipity::Role.all.find_each do |role|
workflow.update_responsibilities(role:,
agents: workflow_agents)
end
end
end
def workflow_agents
[
Sipity::Agent(Hyrax::Group.new(admin_group_name))
].tap do |agent_list|
# The default admin set does not have a creating user
agent_list << Sipity::Agent(creating_user) if creating_user
end
end
# Give registered users deposit access to default admin set
def create_default_access_for(permission_template:, workflow:)
permission_template.access_grants.create(agent_type: 'group', agent_id: ::Ability.registered_group_name, access: Hyrax::PermissionTemplateAccess::DEPOSIT)
deposit = Sipity::Role[Hyrax::RoleRegistry::DEPOSITING]
workflow.update_responsibilities(role: deposit, agents: Hyrax::Group.new('registered'))
end
def default_workflow_importer
Hyrax::Workflow::WorkflowImporter.method(:load_workflow_for)
end
end
end