Class: Valkyrie::Storage::Fedora

Inherits:
Object
  • Object
show all
Defined in:
lib/valkyrie/storage/fedora.rb

Overview

Implements the DataMapper Pattern to store binary data in fedora

Constant Summary collapse

PROTOCOL =
'fedora://'
SLASH =
'/'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection:, base_path: "/", fedora_version: Valkyrie::Persistence::Fedora::DEFAULT_FEDORA_VERSION, fedora_pairtree_count: 0, fedora_pairtree_length: 0) ⇒ Fedora

Returns a new instance of Fedora.

Parameters:

  • connection (Ldp::Client)
  • fedora_pairtree_count (Integer) (defaults to: 0)
  • fedora_pairtree_length (Integer) (defaults to: 0)


12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/valkyrie/storage/fedora.rb', line 12

def initialize(connection:, base_path: "/", fedora_version: Valkyrie::Persistence::Fedora::DEFAULT_FEDORA_VERSION,
               fedora_pairtree_count: 0, fedora_pairtree_length: 0)
  @connection = connection
  @base_path = base_path
  @fedora_version = fedora_version
  @pairtree_count = fedora_pairtree_count
  @pairtree_length = fedora_pairtree_length

  @uri_transformer = if fedora_version >= 6.5 && (pairtree_count * pairtree_length).positive?
                       pairtree_resource_uri_transformer
                     else
                       default_resource_uri_transformer
                     end
end

Instance Attribute Details

#base_pathObject (readonly)

Returns the value of attribute base_path.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def base_path
  @base_path
end

#connectionObject (readonly)

Returns the value of attribute connection.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def connection
  @connection
end

#fedora_versionObject (readonly)

Returns the value of attribute fedora_version.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def fedora_version
  @fedora_version
end

#pairtree_countObject (readonly)

Returns the value of attribute pairtree_count.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def pairtree_count
  @pairtree_count
end

#pairtree_lengthObject (readonly)

Returns the value of attribute pairtree_length.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def pairtree_length
  @pairtree_length
end

#uri_transformerObject (readonly)

Returns the value of attribute uri_transformer.



5
6
7
# File 'lib/valkyrie/storage/fedora.rb', line 5

def uri_transformer
  @uri_transformer
end

Instance Method Details

#current_version_id(id:) ⇒ Object

Parameters:

  • id (Valkyrie::ID)

    A storage ID that’s not a version, to get the version ID of.



153
154
155
156
157
# File 'lib/valkyrie/storage/fedora.rb', line 153

def current_version_id(id:)
  version_list = version_list(fedora_identifier(id: id))
  return nil if version_list.blank?
  valkyrie_identifier(uri: version_list.first["@id"])
end

#delete(id:) ⇒ Object

Delete the file in Fedora associated with the given identifier.

Parameters:



105
106
107
# File 'lib/valkyrie/storage/fedora.rb', line 105

def delete(id:)
  connection.http.delete(fedora_identifier(id: id))
end

#fedora_identifier(id:) ⇒ RDF::URI

Translate the Valkrie ID into a URL for the fedora file

Returns:



208
209
210
211
# File 'lib/valkyrie/storage/fedora.rb', line 208

def fedora_identifier(id:)
  identifier = id.to_s.sub(protocol, "#{connection.http.scheme}://")
  RDF::URI(identifier)
end

#find_by(id:) ⇒ Valkyrie::StorageAdapter::StreamFile

Return the file associated with the given identifier

Parameters:

Returns:

Raises:

  • Valkyrie::StorageAdapter::FileNotFound if nothing is found



53
54
55
# File 'lib/valkyrie/storage/fedora.rb', line 53

def find_by(id:)
  perform_find(id: id)
end

#find_versions(id:) ⇒ Array<Valkyrie::StorageAdapter::StreamFile>

Parameters:

Returns:



92
93
94
95
96
97
98
99
100
101
# File 'lib/valkyrie/storage/fedora.rb', line 92

def find_versions(id:)
  uri = fedora_identifier(id: id)
  version_list = version_list(uri)
  version_list.map do |version|
    id = valkyrie_identifier(uri: version["@id"])
    perform_find(id: id, version_id: id)
  rescue Valkyrie::StorageAdapter::FileNotFound
    nil
  end.compact
end

#handles?(id:) ⇒ Boolean

Returns true if this adapter can handle this type of identifer.

Parameters:

Returns:

  • (Boolean)

    true if this adapter can handle this type of identifer



29
30
31
# File 'lib/valkyrie/storage/fedora.rb', line 29

def handles?(id:)
  id.to_s.start_with?(protocol)
end

#latest_version(identifier) ⇒ Object

Returns a new version identifier to mint. Defaults to version1, but will increment to version2 etc if one found. Only for Fedora 4.



141
142
143
144
145
146
147
148
149
# File 'lib/valkyrie/storage/fedora.rb', line 141

def latest_version(identifier)
  # Only version 4 needs a version ID, 5/6 both mint using timestamps.
  return :not_applicable if fedora_version != 4
  version_list = version_list(identifier)
  return "version1" if version_list.blank?
  last_version = version_list.first["@id"]
  last_version_number = last_version.split("/").last.gsub("version", "").to_i
  "version#{last_version_number + 1}"
end

#mint_version(identifier, version_name = "version1") ⇒ Valkyrie::ID

Versions are created AFTER content is uploaded, except for Fedora 6 which

auto versions.

Parameters:

  • identifier (String)

    Fedora URI to mint a version for.

Returns:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/valkyrie/storage/fedora.rb', line 172

def mint_version(identifier, version_name = "version1")
  response = connection.http.post do |request|
    request.url "#{identifier}/fcr:versions"
    request.headers['Slug'] = version_name if fedora_version == 4
  end
  # If there's a deletion marker, don't return anything. (Fedora 4)
  return nil if response.status == 410
  # This is awful, but versioning is locked to per-second increments,
  # returns a 409 in Fedora 5 if there's a conflict.
  if response.status == 409
    sleep(0.5)
    return mint_version(identifier, version_name)
  end
  raise "Version unable to be created" unless response.status == 201
  valkyrie_identifier(uri: response.headers["location"].gsub("/fcr:metadata", ""))
end

#perform_find(id:, version_id: nil) ⇒ Object



159
160
161
162
163
164
165
166
# File 'lib/valkyrie/storage/fedora.rb', line 159

def perform_find(id:, version_id: nil)
  current_id = Valkyrie::ID.new(id.to_s.split("/fcr:versions").first)
  version_id ||= id if id != current_id
  # No version got passed and we're asking for a current_id, gotta get the
  # version ID
  return perform_find(id: current_id, version_id: current_version_id(id: id) || :empty) if version_id.nil?
  Valkyrie::StorageAdapter::StreamFile.new(id: current_id, io: response(id: id), version_id: version_id)
end

#protocolString

Returns identifier prefix.

Returns:

  • (String)

    identifier prefix



45
46
47
# File 'lib/valkyrie/storage/fedora.rb', line 45

def protocol
  PROTOCOL
end

#supports?(feature) ⇒ Boolean

Returns true if the adapter supports the given feature.

Parameters:

  • feature (Symbol)

    Feature to test for.

Returns:

  • (Boolean)

    true if the adapter supports the given feature



35
36
37
38
39
40
41
42
# File 'lib/valkyrie/storage/fedora.rb', line 35

def supports?(feature)
  return true if feature == :versions
  # Fedora 6 auto versions and you can't delete versions.
  return true if feature == :version_deletion && fedora_version < 6
  # Fedora 6.5+ lists versions for deleted objects
  return true if feature == :list_deleted_versions && fedora_version >= 6.5
  false
end

#upload(file:, original_filename:, resource:, content_type: "application/octet-stream", resource_uri_transformer: uri_transformer, **_extra_arguments) ⇒ Valkyrie::StorageAdapter::StreamFile

Parameters:

  • file (IO)
  • original_filename (String)
  • resource (Valkyrie::Resource)
  • content_type (String) (defaults to: "application/octet-stream")

    content type of file (e.g. ‘image/tiff’) (default=‘application/octet-stream’)

  • resource_uri_transformer (Lambda) (defaults to: uri_transformer)

    transforms the resource’s id (e.g. ‘DDS78RK’) into a uri (optional)

  • extra_arguments (Hash)

    additional arguments which may be passed to other adapters

Returns:



64
65
66
67
68
69
70
71
72
# File 'lib/valkyrie/storage/fedora.rb', line 64

def upload(file:, original_filename:, resource:, content_type: "application/octet-stream", # rubocop:disable Metrics/ParameterLists
           resource_uri_transformer: uri_transformer, **_extra_arguments)
  identifier = resource_uri_transformer.call(resource, base_url) + '/original'
  upload_file(fedora_uri: identifier, io: file, content_type: content_type, original_filename: original_filename)
  # Fedora 6 auto versions, so check to see if there's a version for this
  # initial upload. If not, then mint one (fedora 4/5)
  version_id = current_version_id(id: valkyrie_identifier(uri: identifier)) || mint_version(identifier, latest_version(identifier))
  perform_find(id: Valkyrie::ID.new(identifier.to_s.sub(/^.+\/\//, protocol)), version_id: version_id)
end

#upload_file(fedora_uri:, io:, content_type: "application/octet-stream", original_filename: "default") ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/valkyrie/storage/fedora.rb', line 124

def upload_file(fedora_uri:, io:, content_type: "application/octet-stream", original_filename: "default")
  sha1 = fedora_version >= 5 ? "sha" : "sha1"
  connection.http.put do |request|
    request.url fedora_uri
    request.headers['Content-Type'] = content_type
    io_size = (io.length if io.respond_to?(:length)) || (io.size if io.respond_to?(:size))
    request.headers['Content-Length'] = io_size.to_s if io_size
    request.headers['Content-Disposition'] = "attachment; filename=\"#{original_filename}\""
    request.headers['digest'] = "#{sha1}=#{Digest::SHA1.file(io)}" if io.respond_to?(:to_str)
    request.headers['link'] = "<http://www.w3.org/ns/ldp#NonRDFSource>; rel=\"type\""
    io = Faraday::UploadIO.new(io, content_type, original_filename)
    request.body = io
  end
end

#upload_version(id:, file:) ⇒ Object

Parameters:

  • id (Valkyrie::ID)

    ID of the Valkyrie::StorageAdapter::StreamFile to version.

  • file (IO)


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/valkyrie/storage/fedora.rb', line 77

def upload_version(id:, file:)
  uri = fedora_identifier(id: id)
  # Fedora 6 has auto versioning, so have to sleep if it's too soon after last
  # upload.
  if fedora_version >= 6 && current_version_id(id: id).to_s.split("/").last == Time.current.utc.strftime("%Y%m%d%H%M%S")
    sleep(0.5)
    return upload_version(id: id, file: file)
  end
  upload_file(fedora_uri: uri, io: file)
  version_id = mint_version(uri, latest_version(uri))
  perform_find(id: Valkyrie::ID.new(uri.to_s.sub(/^.+\/\//, protocol)), version_id: version_id)
end

#valkyrie_identifier(uri:) ⇒ Object



213
214
215
216
# File 'lib/valkyrie/storage/fedora.rb', line 213

def valkyrie_identifier(uri:)
  id = uri.to_s.sub("http://", protocol)
  Valkyrie::ID.new(id)
end

#version_list(fedora_uri) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/valkyrie/storage/fedora.rb', line 109

def version_list(fedora_uri)
  version_list = connection.http.get do |request|
    request.url "#{fedora_uri}/fcr:versions"
    request.headers["Accept"] = "application/ld+json"
  end
  return [] unless version_list.success?
  version_graph = JSON.parse(version_list.body)&.first
  if fedora_version == 4
    version_graph&.fetch("http://fedora.info/definitions/v4/repository#hasVersion", [])
  else
    # Fedora 5/6 use Memento.
    version_graph&.fetch("http://www.w3.org/ns/ldp#contains", [])&.sort_by { |x| x["@id"] }&.reverse
  end
end