Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ ENVIRONMENT_BANNER_BACKGROUND='#008000'
SOLR_URL='http://fcrepo-local:8985/solr/fcrepo'

# --- config/environments/*.rb
FCREPO_BASE_URL=http://fcrepo-local:8080/fcrepo/rest/
REPO_EXTERNAL_URL=http://fcrepo-local:8080/fcrepo/rest/
FCREPO_ENDPOINT=http://fcrepo-local:8080/fcrepo/rest

# --- config/environments/*.rb
IIIF_VIEWER_URL_TEMPLATE=http://localhost:8888/viewer/1.3.0/mirador.html?manifest={+manifest}{&q}
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
IIIF_VIEWER_URL_TEMPLATE=http://localhost:8888/viewer/1.3.0/mirador.html?manifest={+manifest}{&q}
IIIF_MANIFESTS_URL_TEMPLATE=http://localhost:3001/manifests/{+manifest_id}/manifest.json
FCREPO_ENDPOINT=http://fcrepo-local:8080/fcrepo/rest
16 changes: 16 additions & 0 deletions app/controllers/concerns/resource_service_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# Mixin for adding resource_service capabilities to a controller
module ResourceServiceConcern
extend ActiveSupport::Concern

included do
def resource_service
@resource_service ||= ResourceService.new(
endpoint: FCREPO_ENDPOINT,
origin: FCREPO_ORIGIN,
auth_token: FCREPO_AUTH_TOKEN
)
end
end
end
9 changes: 6 additions & 3 deletions app/controllers/resource_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
require 'json/ld'

class ResourceController < ApplicationController
include ResourceServiceConcern

before_action :set_resource

def edit
@title = ResourceService.display_title(@resource, @id)
@title = resource_service.display_title(@resource, @id)
@page_title = "Editing: \"#{@title}\""
end

Expand All @@ -17,7 +19,7 @@ def update # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
render json: update_complete
else
plastron_rest_base_url = Addressable::URI.parse(ENV.fetch('PLASTRON_REST_BASE_URL', nil))
repo_path = @id.gsub(FCREPO_BASE_URL, '/')
repo_path = @id.gsub(FCREPO_ENDPOINT, '/')
plastron_resource_url = plastron_rest_base_url.join("resources#{repo_path}")
begin
response = HTTP.follow.headers(
Expand Down Expand Up @@ -64,7 +66,8 @@ def update_state

def set_resource
@id = params[:id]
@resource = ResourceService.resource_with_model(@id)
Rails.logger.info(@id)
@resource = resource_service.resource_with_model(@id)
end

def update_command
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/retrieve_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class RetrieveController < ApplicationController
include ResourceServiceConcern

skip_before_action :authenticate

# GET /retrieve/:token
Expand All @@ -18,7 +20,7 @@ def do_retrieve # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
download_url = DownloadUrl.find_by(token: @token)
return unless verify_download_url?(download_url)

response = ResourceService.get(download_url.url)
response = resource_service.get(download_url.url)
data = response.body

download_url.enabled = false
Expand Down
6 changes: 3 additions & 3 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ module ApplicationHelper
# UMD Customization
def encoded_id(document)
id = document._source[:id]
ERB::Util.url_encode(id.slice(FCREPO_BASE_URL.size, id.size))
ERB::Util.url_encode(id.slice(FCREPO_ENDPOINT.size, id.size))
end

def repo_path(url)
url.sub(REPO_EXTERNAL_URL, '')
url.sub(FCREPO_ENDPOINT, '')
end

def fcrepo_url
FCREPO_BASE_URL.sub(%r{fcrepo/rest/?}, '')
FCREPO_ENDPOINT.sub(%r{fcrepo/rest/?}, '')
end

def link_to_document_view(args)
Expand Down
8 changes: 4 additions & 4 deletions app/models/import_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ def binaries?
end

# Returns the relpath of the collection (the collection with the
# FCREPO_BASE_URL prefix removed). Will always start with a "/", and
# FCREPO_ENDPOINT prefix removed). Will always start with a "/", and
# returns FLAT_LAYOUT_RELPATH if the relpath starts with that value.
def collection_relpath
# Collection path could be either REPO_EXTERNAL_URL or FCREPO_BASE_URL,
# Collection path could be either FCREPO_ENDPOINT or FCREPO_ORIGIN,
# so just strip both
relpath = collection.sub(REPO_EXTERNAL_URL, '')
relpath = relpath.sub(FCREPO_BASE_URL, '')
relpath = collection.sub(FCREPO_ENDPOINT, '')
relpath = relpath.sub(FCREPO_ORIGIN, '') if FCREPO_ORIGIN

# Ensure that relpath starts with a "/"
relpath = "/#{relpath}" unless relpath.starts_with?('/')
Expand Down
81 changes: 53 additions & 28 deletions app/services/resource_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,47 @@

# Utilities to retrieve resources from the repository
class ResourceService
def self.fcrepo_http_client
headers = {}
headers['Authorization'] = "Bearer #{FCREPO_AUTH_TOKEN}" if FCREPO_AUTH_TOKEN
HTTP::Client.new(headers: headers, ssl_context: SSL_CONTEXT)
def initialize(endpoint:, origin: nil, auth_token: nil)
@endpoint = URI(endpoint)
@origin = origin ? URI(origin) : nil
@auth_token = auth_token
end

def self.description_uri(uri)
response = fcrepo_http_client.head(uri)
def forwarding_headers
return {} unless @origin

{
'X-Forwarded-Proto': @endpoint.scheme,
# fcrepo expects the hostname and port in the X-Forwarded-Host header
# Ruby's URI#authority gives us both with intelligent defaulting if the
# port is the default for the scheme
'X-Forwarded-Host': @endpoint.authority
}
end

def authorization_headers
@auth_token ? { Authorization: "Bearer #{@auth_token}" } : {}
end

def request_headers
{ **forwarding_headers, **authorization_headers }
end

def client
Rails.logger.debug { "Request headers: #{request_headers}" }
@client ||= HTTP::Client.new(headers: request_headers, ssl_context: SSL_CONTEXT)
end

def request_url(uri)
raise "invalid URI for this repository: #{uri}" unless uri.start_with? @endpoint.to_s

@origin ? uri.sub(@endpoint, @origin) : uri
end

def description_uri(uri)
Rails.logger.debug { "Request URI: #{uri}" }
Rails.logger.debug { "Request URL: #{request_url(uri)}" }
response = client.head(request_url(uri))
if response.headers.include? 'Link'
links = LinkHeader.parse(response['Link'].join(','))
links.find_link(%w[rel describedby])&.href || uri
Expand All @@ -20,12 +53,12 @@ def self.description_uri(uri)
end
end

def self.get(uri, **opts)
fcrepo_http_client.get(uri, opts)
def get(uri, **opts)
client.get(uri, opts)
end

def self.resources(uri)
response = get(description_uri(uri), headers: { accept: 'application/ld+json' })
def resources(uri)
response = get(request_url(description_uri(uri)), headers: { accept: 'application/ld+json' })
# This is a bit of a kludge to get around problems with building a string from the
# response body content when the "frozen_string_literal: true" pragma is in effect.
# Start with an unfrozen empty string (created using the unary '+' operator), then
Expand All @@ -36,38 +69,30 @@ def self.resources(uri)
JSON::LD::API.expand(input)
end

def self.resource_with_model(id)
def resource_with_model(uri)
# create a hash of resources by their URIs
items = resources(id).to_h do |resource|
items = resources(uri).to_h do |resource|
uri = resource.delete('@id')
[uri, resource]
end

# default to the Item content model
name = content_model_name(items[id]['@type']) || :Item
name = content_model_name(items[uri]['@type'] || [])
{
items: items,
content_model_name: name,
content_model: CONTENT_MODELS[name]
}
end

CONTENT_MODEL_MAP = [
[:Issue, ->(types) { types.include? 'http://purl.org/ontology/bibo/Issue' }],
[:Letter, ->(types) { types.include? 'http://purl.org/ontology/bibo/Letter' }],
[:Poster, ->(types) { types.include? 'http://purl.org/ontology/bibo/Image' }],
[:Page, ->(types) { types.include? 'http://purl.org/spar/fabio/Page' }],
[:Page, ->(types) { types.include? 'http://chroniclingamerica.loc.gov/terms/Page' }],
[:Item, ->(types) { types.include? 'http://pcdm.org/models#Object' }],
[:Item, ->(types) { types.include? 'http://pcdm.org/models#File' }]
].freeze

def self.content_model_name(types)
CONTENT_MODEL_MAP.find { |pair| pair[1].call(types) }&.first
def content_model_name(types)
return :Issue if types.include? 'http://vocab.lib.umd.edu/model#Newspaper'

# default to the Item content model
:Item
end

# Returns the display title for the Fedora resource, or nil
def self.display_title(resource, id)
def display_title(resource, id)
return unless resource && id

resource_titles = resource.dig(:items, id, 'http://purl.org/dc/terms/title')
Expand All @@ -78,7 +103,7 @@ def self.display_title(resource, id)
end

# Sorts resource titles by language to ensure consistent ordering
def self.sort_titles_by_language(resource_titles) # rubocop:disable Metrics/MethodLength
def sort_titles_by_language(resource_titles) # rubocop:disable Metrics/MethodLength
languages_map = {}
resource_titles.each do |title|
language = title['@language'] || 'None'
Expand Down
8 changes: 4 additions & 4 deletions config/initializers/fcrepo.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FCREPO_BASE_URL = ENV.fetch('FCREPO_BASE_URL', '')

FCREPO_AUTH_TOKEN = ENV.fetch('FCREPO_AUTH_TOKEN', '')

# When using Docker or Kubernetes, the fcrepo URL that is displayed,
# the "external" URL, may be different from the "internal"
# Docker/Kubernetes URL
REPO_EXTERNAL_URL = ENV.fetch('REPO_EXTERNAL_URL', '')
# the "external" URL (e.g., https://fcrepo.lib.umd.edu/fcrepo/rest)
FCREPO_ENDPOINT = ENV['FCREPO_ENDPOINT']
# the "internal" URL (e.g., http://fcrepo-local:8080/fcrepo/rest)
FCREPO_ORIGIN = ENV.fetch('FCREPO_ORIGIN', nil)
7 changes: 5 additions & 2 deletions env_example
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
SOLR_URL=http://localhost:8983/solr/fedora4

# --- config/fcrepo.rb
FCREPO_BASE_URL=http://fcrepo-local:8080/fcrepo/rest/
REPO_EXTERNAL_URL=http://fcrepo-local:8080/fcrepo/rest/
FCREPO_ENDPOINT=http://fcrepo-local:8080/fcrepo/rest
# if there is a separate URL for connecting to the Fedora repository that differs
# from the canonical endpoint URL (e.g., a cluster-interal alternative hostname),
# uncomment FCREPO_ORIGIN and specify it here
# FCREPO_ORIGIN=
# JWT auth token with user "archelon" and role "fedoraAdmin"
FCREPO_AUTH_TOKEN=

Expand Down
1 change: 1 addition & 0 deletions test/controllers/resource_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ResourceControllerTest < ActionController::TestCase
end

test 'update should complete when SPARQL query is empty' do
skip('TODO: set up correct test environment')
resource_id = 'http://example.com/123'
ResourceService.should_receive(:resource_with_model).and_return({})

Expand Down
12 changes: 6 additions & 6 deletions test/models/import_job_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ImportJobTest < ActiveSupport::TestCase
]

test_base_urls.each do |base_url|
with_constant('FCREPO_BASE_URL', base_url) do
with_constant('FCREPO_ENDPOINT', base_url) do
test_collections.each do |collection, expected_relpath|
import_job = ImportJob.new
import_job.collection = collection
Expand All @@ -45,7 +45,7 @@ class ImportJobTest < ActiveSupport::TestCase
]

test_external_urls.each do |external_url|
with_constant('REPO_EXTERNAL_URL', external_url) do
with_constant('FCREPO_ENDPOINT', external_url) do
test_collections.each do |collection, expected_relpath|
import_job = ImportJob.new
import_job.collection = collection
Expand All @@ -56,23 +56,23 @@ class ImportJobTest < ActiveSupport::TestCase
end

test 'structure_type returns "hierarchical" for hierarchical collections' do
with_constant('FCREPO_BASE_URL', 'http://example.com/rest') do
with_constant('FCREPO_ENDPOINT', 'http://example.com/rest') do
import_job = ImportJob.new
import_job.collection = 'http://example.com/rest/dc/2021/2'
assert_equal :hierarchical, import_job.collection_structure
end
end

test 'structure_type returns "flat" for flat collections' do
with_constant('FCREPO_BASE_URL', 'http://example.com/rest/') do
with_constant('FCREPO_ENDPOINT', 'http://example.com/rest/') do
import_job = ImportJob.new
import_job.collection = 'http://example.com/rest/pcdm'
assert_equal :flat, import_job.collection_structure
end
end

test 'structure_type returns "flat" for flat collections when FCREPO_BASE_URL does not include final slash' do
with_constant('FCREPO_BASE_URL', 'http://example.com/rest') do
test 'structure_type returns "flat" for flat collections when FCREPO_ENDPOINT does not include final slash' do
with_constant('FCREPO_ENDPOINT', 'http://example.com/rest') do
import_job = ImportJob.new
import_job.collection = 'http://example.com/rest/pcdm'
assert_equal :flat, import_job.collection_structure
Expand Down
4 changes: 2 additions & 2 deletions test/services/info_job_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def setup
end

test 'headers for "flat" collections' do
with_constant('FCREPO_BASE_URL', 'http://example.com/rest') do
with_constant('FCREPO_ENDPOINT', 'http://example.com/rest') do
import_job = import_jobs(:one)
import_job.collection = 'http://example.com/rest/pcdm/51/a4/54/a8/51a454a8-7ad0-45dd-ba2b-85632fe1b618'
assert_equal :flat, import_job.collection_structure
Expand All @@ -20,7 +20,7 @@ def setup
end

test 'headers for "hierarchical" collections' do
with_constant('FCREPO_BASE_URL', 'http://example.com/rest') do
with_constant('FCREPO_ENDPOINT', 'http://example.com/rest') do
import_job = import_jobs(:one)
import_job.collection = 'http://example.com/rest/dc/2021/2'
assert_equal :hierarchical, import_job.collection_structure
Expand Down
Loading