Skip to content

Commit

Permalink
Fedora 5 support (#617)
Browse files Browse the repository at this point in the history
Provides support for Fedora 5 by testing both versions 4 and 5.

Details:

* Adds Fedora 5 compatibility flag FEDORA5_COMPAT (if set, changes behavior for Fedora 5.0)
* Fixes UTC/localtime mismatch bug
* Adds deprecation warnings if fedora_version is not set
  • Loading branch information
escowles authored and awead committed Dec 5, 2018
1 parent 3e79977 commit 2e882ef
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 284 deletions.
15 changes: 11 additions & 4 deletions .docker-stack/valkyrie-development/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
---
version: '3.4'
volumes:
fedora:
fedora4:
fedora5:
db:
solr_repo:
solr_index:
services:
fedora:
image: nulib/fcrepo4
fedora4:
image: nulib/fcrepo4:4.7.5
volumes:
- fedora:/data
- fedora4:/data
ports:
- 8986:8080
fedora5:
image: nulib/fcrepo4:5.0.0-RC-2
volumes:
- fedora5:/data
ports:
- 8996:8080
db:
image: healthcheck/postgres:alpine
volumes:
Expand Down
15 changes: 11 additions & 4 deletions .docker-stack/valkyrie-test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
---
version: '3.4'
volumes:
fedora:
fedora4:
fedora5:
db:
solr_repo:
solr_index:
services:
fedora:
image: nulib/fcrepo4
fedora4:
image: nulib/fcrepo4:4.7.5
volumes:
- fedora:/data
- fedora4:/data
ports:
- 8988:8080
fedora5:
image: nulib/fcrepo4:5.0.0-RC-2
volumes:
- fedora5:/data
ports:
- 8998:8080
db:
image: healthcheck/postgres:alpine
volumes:
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ By default, it is assumed that a Valkyrie repository implementation shall use a
1. Run `rake docker:test:down` to stop the server stack
* The test stack cleans up after itself on exit.

## Fedora 5 Compatibility
When configuring your adapter, include the `fedora_version` parameter in your metadata or storage adapter config. If Fedora requires auth, you can also include that in the URL, e.g.:
```
Valkyrie::Storage::Fedora.new(connection: Ldp::Client.new("http://fedoraAdmin:fedoraAdmin@localhost:8988/rest"), fedora_version: 5)
```

The development and test stacks use fully contained virtual volumes and bind all services to different ports, so they can be running at the same time without issue.

## Get Help
Expand Down
11 changes: 8 additions & 3 deletions lib/valkyrie/persistence/fedora/metadata_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ module Valkyrie::Persistence::Fedora
# schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new(title: RDF::URI("http://bad.com/title"))
# )
class MetadataAdapter
attr_reader :connection, :base_path, :schema
attr_reader :connection, :base_path, :schema, :fedora_version

# @param [Ldp::Client] connection
# @param [String] base_path
# @param [Valkyrie::Persistence::Fedora::PermissiveSchema] schema
def initialize(connection:, base_path: "/", schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new)
# @param [Integer] fedora_version
def initialize(connection:, base_path: "/", schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new, fedora_version:)
@connection = connection
@base_path = base_path
@schema = schema
@fedora_version = fedora_version

warn "[DEPRECATION] `fedora_version` will default to 5 in the next major release." unless fedora_version
end

# Construct the query service object using this adapter
Expand Down Expand Up @@ -56,7 +60,8 @@ def uri_to_id(uri)
# @param [RDF::URI] id the Valkyrie ID
# @return [RDF::URI]
def id_to_uri(id)
RDF::URI("#{connection_prefix}/#{pair_path(id)}/#{CGI.escape(id.to_s)}")
prefix = fedora_version == 5 ? "" : "#{pair_path(id)}/"
RDF::URI("#{connection_prefix}/#{prefix}#{CGI.escape(id.to_s)}")
end

# Generate the pairtree path for a given Valkyrie ID
Expand Down
2 changes: 1 addition & 1 deletion lib/valkyrie/persistence/fedora/query_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def find_parents(resource:)
# @return [Array<RDF::URI>]
def include_uris
[
::RDF::Vocab::Fcrepo4.InboundReferences
adapter.fedora_version == 5 ? "http://fedora.info/definitions/fcrepo#PreferInboundReferences" : ::RDF::Vocab::Fcrepo4.InboundReferences
]
end

Expand Down
11 changes: 8 additions & 3 deletions lib/valkyrie/storage/fedora.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
module Valkyrie::Storage
# Implements the DataMapper Pattern to store binary data in fedora
class Fedora
attr_reader :connection, :base_path
attr_reader :connection, :base_path, :fedora_version
PROTOCOL = 'fedora://'

# @param [Ldp::Client] connection
def initialize(connection:, base_path: "/")
def initialize(connection:, base_path: "/", fedora_version:)
@connection = connection
@base_path = base_path
@fedora_version = fedora_version

warn "[DEPRECATION] `fedora_version` will default to 5 in the next major release." unless fedora_version
end

# @param id [Valkyrie::ID]
Expand All @@ -31,11 +34,13 @@ def find_by(id:)
# @return [Valkyrie::StorageAdapter::StreamFile]
def upload(file:, original_filename:, resource:)
identifier = id_to_uri(resource.id) + '/original'
sha1 = fedora_version == 5 ? "sha" : "sha1"
connection.http.put do |request|
request.url identifier
request.headers['Content-Type'] = file.content_type
request.headers['Content-Disposition'] = "attachment; filename=\"#{original_filename}\""
request.headers['digest'] = "sha1=#{Digest::SHA1.file(file)}"
request.headers['digest'] = "#{sha1}=#{Digest::SHA1.file(file)}"
request.headers['link'] = "<http://www.w3.org/ns/ldp#NonRDFSource>; rel=\"type\""
request.body = file.tempfile.read
end
find_by(id: Valkyrie::ID.new(identifier.to_s.sub(/^.+\/\//, PROTOCOL)))
Expand Down
29 changes: 29 additions & 0 deletions spec/support/fedora_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true
module FedoraHelper
def fedora_adapter_config(base_path:, schema: nil, fedora_version: 4)
port = fedora_version == 4 ? 8988 : 8998
opts = {
base_path: base_path,
connection: ::Ldp::Client.new("http://#{fedora_auth}localhost:#{port}/rest"),
fedora_version: fedora_version
}
opts[:schema] = schema if schema
opts
end

def fedora_auth
"fedoraAdmin:fedoraAdmin@"
end

def wipe_fedora!(base_path:, fedora_version: 4)
Valkyrie::Persistence::Fedora::MetadataAdapter.new(fedora_adapter_config(base_path: base_path, fedora_version: fedora_version)).persister.wipe!
end
end

RSpec.configure do |config|
config.before do
wipe_fedora!(base_path: "test_fed", fedora_version: 4)
wipe_fedora!(base_path: "test_fed", fedora_version: 5)
end
config.include FedoraHelper
end
6 changes: 0 additions & 6 deletions spec/support/wipe_raw_fedora_adapter.rb

This file was deleted.

96 changes: 53 additions & 43 deletions spec/valkyrie/persistence/fedora/metadata_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,66 @@
require 'valkyrie/specs/shared_specs'

RSpec.describe Valkyrie::Persistence::Fedora::MetadataAdapter do
let(:adapter) { described_class.new(connection: ::Ldp::Client.new("http://localhost:8988/rest"), base_path: "test_fed") }
it_behaves_like "a Valkyrie::MetadataAdapter"
[4, 5].each do |fedora_version|
context "fedora #{fedora_version}" do
let(:version) { fedora_version }
let(:adapter) { described_class.new(fedora_adapter_config(base_path: "test_fed", fedora_version: version)) }
it_behaves_like "a Valkyrie::MetadataAdapter"

describe "#schema" do
context "by default" do
specify { expect(adapter.schema).to be_a Valkyrie::Persistence::Fedora::PermissiveSchema }
end
describe "#schema" do
context "by default" do
specify { expect(adapter.schema).to be_a Valkyrie::Persistence::Fedora::PermissiveSchema }
end

context "with a custom schema" do
let(:adapter) { described_class.new(connection: ::Ldp::Client.new("http://localhost:8988/rest"), base_path: "test_fed", schema: "custom-schema") }
specify { expect(adapter.schema).to eq("custom-schema") }
end
end
context "with a custom schema" do
let(:adapter) { described_class.new(fedora_adapter_config(base_path: "test_fed", schema: "custom-schema", fedora_version: version)) }
specify { expect(adapter.schema).to eq("custom-schema") }
end
end

describe "#id_to_uri" do
it "converts ids with a slash" do
id = "test/default"
expect(adapter.id_to_uri(id).to_s).to eq "http://localhost:8988/rest/test_fed/te/st/test%2Fdefault"
end
end
describe "#id_to_uri" do
it "converts ids with a slash" do
id = "test/default"
if adapter.fedora_version == 4

describe "#uri_to_id" do
it "converts ids with a slash" do
uri = adapter.id_to_uri("test/default")
expect(adapter.uri_to_id(uri).to_s).to eq "test/default"
end
end
expect(adapter.id_to_uri(id).to_s).to eq "http://localhost:8988/rest/test_fed/te/st/test%2Fdefault"
else
expect(adapter.id_to_uri(id).to_s).to eq "http://localhost:8998/rest/test_fed/test%2Fdefault"
end
end
end

describe "#pair_path" do
it "creates pairs until the first dash" do
expect(adapter.pair_path('abcdef-ghijkl')).to eq('ab/cd/ef')
end
it "creates pairs until the first slash" do
expect(adapter.pair_path('admin_set/default')).to eq('ad/mi/n_/se/t')
end
end
describe "#uri_to_id" do
it "converts ids with a slash" do
uri = adapter.id_to_uri("test/default")
expect(adapter.uri_to_id(uri).to_s).to eq "test/default"
end
end

describe "#id" do
it "creates an md5 hash from the connection_prefix" do
expected = Digest::MD5.hexdigest adapter.connection_prefix
expect(adapter.id.to_s).to eq expected
end
end
describe "#pair_path" do
it "creates pairs until the first dash" do
expect(adapter.pair_path('abcdef-ghijkl')).to eq('ab/cd/ef')
end
it "creates pairs until the first slash" do
expect(adapter.pair_path('admin_set/default')).to eq('ad/mi/n_/se/t')
end
end

describe "#id" do
it "creates an md5 hash from the connection_prefix" do
expected = Digest::MD5.hexdigest adapter.connection_prefix
expect(adapter.id.to_s).to eq expected
end
end

# rubocop:disable Metrics/LineLength
describe "#standardize_query_result?" do
it "throws a deprecation warning when it's set to false" do
allow(Valkyrie.config).to receive(:standardize_query_result).and_return(false)
expect { adapter.standardize_query_result? }.to output(/Please enable query normalization to avoid inconsistent results between different adapters by adding `standardize_query_results: true` to your environment block in config\/valkyrie.yml. This will be the behavior in Valkyrie 2.0./).to_stderr
# rubocop:disable Metrics/LineLength
describe "#standardize_query_result?" do
it "throws a deprecation warning when it's set to false" do
allow(Valkyrie.config).to receive(:standardize_query_result).and_return(false)
expect { adapter.standardize_query_result? }.to output(/Please enable query normalization to avoid inconsistent results between different adapters by adding `standardize_query_results: true` to your environment block in config\/valkyrie.yml. This will be the behavior in Valkyrie 2.0./).to_stderr
end
end
# rubocop:enable Metrics/LineLength
end
end
# rubocop:enable Metrics/LineLength
end
77 changes: 40 additions & 37 deletions spec/valkyrie/persistence/fedora/persister/model_converter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,46 @@
require 'spec_helper'

RSpec.describe Valkyrie::Persistence::Fedora::Persister::ModelConverter do
let(:adapter) do
Valkyrie::Persistence::Fedora::MetadataAdapter.new(
connection: ::Ldp::Client.new("http://localhost:8988/rest"),
base_path: "test_fed",
schema: schema
)
end

let(:resource) { SampleResource.new(title: "My Title") }
let(:converter) { described_class.new(resource: resource, adapter: adapter) }

before do
class SampleResource < Valkyrie::Resource
include Valkyrie::Resource::AccessControls
attribute :title
end
end

after do
Object.send(:remove_const, :SampleResource)
end

context "with the default schema" do
let(:schema) { Valkyrie::Persistence::Fedora::PermissiveSchema.new }
let(:query) { converter.convert.graph.query(predicate: RDF::URI("http://example.com/predicate/title")) }

it "persists to Fedora using a fake predicate" do
expect(query.first.object.to_s).to eq("My Title")
end
end

context "with a defined schema" do
let(:schema) { Valkyrie::Persistence::Fedora::PermissiveSchema.new(title: ::RDF::Vocab::DC.title) }
let(:query) { converter.convert.graph.query(predicate: ::RDF::Vocab::DC.title) }

it "persists to Fedora using the defined predicate" do
expect(query.first.object.to_s).to eq("My Title")
[4, 5].each do |fedora_version|
context "fedora #{fedora_version}" do
let(:version) { fedora_version }
let(:adapter) do
Valkyrie::Persistence::Fedora::MetadataAdapter.new(
fedora_adapter_config(base_path: "test_fed", schema: schema, fedora_version: version)
)
end

let(:resource) { SampleResource.new(title: "My Title") }
let(:converter) { described_class.new(resource: resource, adapter: adapter) }

before do
class SampleResource < Valkyrie::Resource
include Valkyrie::Resource::AccessControls
attribute :title
end
end

after do
Object.send(:remove_const, :SampleResource)
end

context "with the default schema" do
let(:schema) { Valkyrie::Persistence::Fedora::PermissiveSchema.new }
let(:query) { converter.convert.graph.query(predicate: RDF::URI("http://example.com/predicate/title")) }

it "persists to Fedora using a fake predicate" do
expect(query.first.object.to_s).to eq("My Title")
end
end

context "with a defined schema" do
let(:schema) { Valkyrie::Persistence::Fedora::PermissiveSchema.new(title: ::RDF::Vocab::DC.title) }
let(:query) { converter.convert.graph.query(predicate: ::RDF::Vocab::DC.title) }

it "persists to Fedora using the defined predicate" do
expect(query.first.object.to_s).to eq("My Title")
end
end
end
end
end
Loading

0 comments on commit 2e882ef

Please sign in to comment.