valkyrie 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -3
- data/.rubocop.yml +28 -0
- data/Gemfile +6 -1
- data/README.md +145 -10
- data/Rakefile +59 -1
- data/bin/console +1 -1
- data/config/valkyrie.yml +8 -0
- data/db/config.yml +17 -0
- data/db/migrate/20160111215816_enable_uuid_extension.rb +6 -0
- data/db/migrate/20161007101725_create_orm_resources.rb +10 -0
- data/db/migrate/20170124135846_add_model_type_to_orm_resources.rb +6 -0
- data/db/migrate/20170531004548_change_model_type_to_internal_model.rb +6 -0
- data/db/schema.rb +65 -0
- data/db/seeds.rb +8 -0
- data/lib/config/database_connection.rb +15 -0
- data/lib/generators/valkyrie/resource_generator.rb +27 -0
- data/lib/generators/valkyrie/templates/resource.rb.erb +9 -0
- data/lib/generators/valkyrie/templates/resource_spec.rb.erb +13 -0
- data/lib/valkyrie.rb +76 -1
- data/lib/valkyrie/adapter_container.rb +12 -0
- data/lib/valkyrie/change_set.rb +84 -0
- data/lib/valkyrie/decorators/decorator_list.rb +15 -0
- data/lib/valkyrie/decorators/decorator_with_arguments.rb +14 -0
- data/lib/valkyrie/derivative_service.rb +42 -0
- data/lib/valkyrie/engine.rb +10 -0
- data/lib/valkyrie/file_characterization_service.rb +42 -0
- data/lib/valkyrie/id.rb +32 -0
- data/lib/valkyrie/indexers/access_controls_indexer.rb +19 -0
- data/lib/valkyrie/local_file_service.rb +11 -0
- data/lib/valkyrie/metadata_adapter.rb +22 -0
- data/lib/valkyrie/persist_derivatives.rb +29 -0
- data/lib/valkyrie/persistence.rb +14 -0
- data/lib/valkyrie/persistence/buffered_persister.rb +28 -0
- data/lib/valkyrie/persistence/composite_persister.rb +29 -0
- data/lib/valkyrie/persistence/delete_tracking_buffer.rb +21 -0
- data/lib/valkyrie/persistence/fedora.rb +11 -0
- data/lib/valkyrie/persistence/fedora/list_node.rb +88 -0
- data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +45 -0
- data/lib/valkyrie/persistence/fedora/ordered_list.rb +146 -0
- data/lib/valkyrie/persistence/fedora/ordered_reader.rb +28 -0
- data/lib/valkyrie/persistence/fedora/persister.rb +47 -0
- data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +199 -0
- data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +338 -0
- data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +21 -0
- data/lib/valkyrie/persistence/fedora/query_service.rb +80 -0
- data/lib/valkyrie/persistence/memory.rb +8 -0
- data/lib/valkyrie/persistence/memory/metadata_adapter.rb +22 -0
- data/lib/valkyrie/persistence/memory/persister.rb +58 -0
- data/lib/valkyrie/persistence/memory/query_service.rb +86 -0
- data/lib/valkyrie/persistence/postgres.rb +6 -0
- data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +20 -0
- data/lib/valkyrie/persistence/postgres/orm.rb +9 -0
- data/lib/valkyrie/persistence/postgres/orm/resource.rb +7 -0
- data/lib/valkyrie/persistence/postgres/orm_converter.rb +118 -0
- data/lib/valkyrie/persistence/postgres/persister.rb +33 -0
- data/lib/valkyrie/persistence/postgres/queries.rb +8 -0
- data/lib/valkyrie/persistence/postgres/queries/find_inverse_references_query.rb +31 -0
- data/lib/valkyrie/persistence/postgres/queries/find_members_query.rb +33 -0
- data/lib/valkyrie/persistence/postgres/queries/find_references_query.rb +33 -0
- data/lib/valkyrie/persistence/postgres/query_service.rb +53 -0
- data/lib/valkyrie/persistence/postgres/resource_converter.rb +18 -0
- data/lib/valkyrie/persistence/postgres/resource_factory.rb +30 -0
- data/lib/valkyrie/persistence/solr.rb +6 -0
- data/lib/valkyrie/persistence/solr/metadata_adapter.rb +42 -0
- data/lib/valkyrie/persistence/solr/model_converter.rb +270 -0
- data/lib/valkyrie/persistence/solr/orm_converter.rb +252 -0
- data/lib/valkyrie/persistence/solr/persister.rb +32 -0
- data/lib/valkyrie/persistence/solr/queries.rb +11 -0
- data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +16 -0
- data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +33 -0
- data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +24 -0
- data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +30 -0
- data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +43 -0
- data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +34 -0
- data/lib/valkyrie/persistence/solr/query_service.rb +48 -0
- data/lib/valkyrie/persistence/solr/repository.rb +36 -0
- data/lib/valkyrie/persistence/solr/resource_factory.rb +24 -0
- data/lib/valkyrie/rdf_patches.rb +17 -0
- data/lib/valkyrie/resource.rb +106 -0
- data/lib/valkyrie/resource/access_controls.rb +13 -0
- data/lib/valkyrie/specs/shared_specs.rb +10 -0
- data/lib/valkyrie/specs/shared_specs/change_set_persister.rb +60 -0
- data/lib/valkyrie/specs/shared_specs/derivative_service.rb +30 -0
- data/lib/valkyrie/specs/shared_specs/file.rb +12 -0
- data/lib/valkyrie/specs/shared_specs/file_characterization_service.rb +33 -0
- data/lib/valkyrie/specs/shared_specs/metadata_adapter.rb +10 -0
- data/lib/valkyrie/specs/shared_specs/persister.rb +154 -0
- data/lib/valkyrie/specs/shared_specs/queries.rb +128 -0
- data/lib/valkyrie/specs/shared_specs/resource.rb +71 -0
- data/lib/valkyrie/specs/shared_specs/storage_adapter.rb +44 -0
- data/lib/valkyrie/storage.rb +8 -0
- data/lib/valkyrie/storage/disk.rb +55 -0
- data/lib/valkyrie/storage/fedora.rb +71 -0
- data/lib/valkyrie/storage/memory.rb +31 -0
- data/lib/valkyrie/storage_adapter.rb +100 -0
- data/lib/valkyrie/types.rb +34 -0
- data/lib/valkyrie/value_mapper.rb +67 -0
- data/lib/valkyrie/version.rb +2 -1
- data/lib/valkyrie/vocab/pcdm_use.rb +73 -0
- data/valkyrie.gemspec +33 -7
- metadata +462 -7
- data/.travis.yml +0 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie
|
|
3
|
+
class LocalFileService
|
|
4
|
+
# @param [String] file_name path to the file
|
|
5
|
+
# @param [Hash] _options
|
|
6
|
+
# @yield [File] opens the file and yields it to the block
|
|
7
|
+
def self.call(file_name, _options)
|
|
8
|
+
yield File.open(file_name)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie
|
|
3
|
+
class MetadataAdapter
|
|
4
|
+
class_attribute :adapters
|
|
5
|
+
self.adapters = {}
|
|
6
|
+
class << self
|
|
7
|
+
# Register an adapter by a short name.
|
|
8
|
+
# @param adapter [#persister,#query_service] Adapter to register.
|
|
9
|
+
# @param short_name [Symbol] Name to register it under.
|
|
10
|
+
def register(adapter, short_name)
|
|
11
|
+
adapters[short_name.to_sym] = adapter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Find an adapter by its short name.
|
|
15
|
+
# @param short_name [Symbol]
|
|
16
|
+
# @return [#persister,#query_service]
|
|
17
|
+
def find(short_name)
|
|
18
|
+
adapters[short_name.to_sym]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie
|
|
3
|
+
class PersistDerivatives < Hydra::Derivatives::PersistOutputFileService
|
|
4
|
+
# Persists a derivative to the local file system.
|
|
5
|
+
# This Service conforms to the signature of `Hydra::Derivatives::PersistOutputFileService`.
|
|
6
|
+
# This service is an alternative to the default Hydra::Derivatives::PersistOutputFileService.
|
|
7
|
+
# This service will always update existing and does not do versioning of persisted files.
|
|
8
|
+
#
|
|
9
|
+
# @param [#read] stream the derivative filestream
|
|
10
|
+
# @param [Hash] directives
|
|
11
|
+
# @option directives [String] :url a url to the file destination
|
|
12
|
+
def self.call(stream, directives)
|
|
13
|
+
output_file(directives) do |output|
|
|
14
|
+
IO.copy_stream(stream, output)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Open the output file to write and yield the block to the
|
|
19
|
+
# file. It makes the directories in the path if necessary.
|
|
20
|
+
def self.output_file(directives, &blk)
|
|
21
|
+
raise ArgumentError, "No :url was provided in the transcoding directives" unless directives.key?(:url)
|
|
22
|
+
uri = URI(directives.fetch(:url))
|
|
23
|
+
raise ArgumentError, "Must provide a file uri" unless uri.scheme == 'file'
|
|
24
|
+
output_file_dir = File.dirname(uri.path)
|
|
25
|
+
FileUtils.mkdir_p(output_file_dir) unless File.directory?(output_file_dir)
|
|
26
|
+
File.open(uri.path, 'wb', &blk)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie
|
|
3
|
+
module Persistence
|
|
4
|
+
require 'valkyrie/persistence/memory'
|
|
5
|
+
require 'valkyrie/persistence/postgres'
|
|
6
|
+
require 'valkyrie/persistence/solr'
|
|
7
|
+
require 'valkyrie/persistence/fedora'
|
|
8
|
+
require 'valkyrie/persistence/composite_persister'
|
|
9
|
+
require 'valkyrie/persistence/delete_tracking_buffer'
|
|
10
|
+
require 'valkyrie/persistence/buffered_persister'
|
|
11
|
+
class ObjectNotFoundError < StandardError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence
|
|
3
|
+
class BufferedPersister
|
|
4
|
+
attr_reader :persister, :buffer_class
|
|
5
|
+
delegate :adapter, to: :persister
|
|
6
|
+
def initialize(persister, buffer_class: Valkyrie::Persistence::DeleteTrackingBuffer)
|
|
7
|
+
@persister = persister
|
|
8
|
+
@buffer_class = buffer_class
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def save(resource:)
|
|
12
|
+
persister.save(resource: resource)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save_all(resources:)
|
|
16
|
+
persister.save_all(resources: resources)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete(resource:)
|
|
20
|
+
persister.delete(resource: resource)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_buffer
|
|
24
|
+
memory_buffer = buffer_class.new
|
|
25
|
+
yield [Valkyrie::Persistence::CompositePersister.new(self, memory_buffer.persister), memory_buffer]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence
|
|
3
|
+
##
|
|
4
|
+
# Wrap up multiple persisters under a common interface, to transparently
|
|
5
|
+
# persist to multiple places at once.
|
|
6
|
+
class CompositePersister
|
|
7
|
+
attr_reader :persisters
|
|
8
|
+
def initialize(*persisters)
|
|
9
|
+
@persisters = persisters
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# (see Valkyrie::Persistence::Memory::Persister#save)
|
|
13
|
+
def save(resource:)
|
|
14
|
+
persisters.inject(resource) { |m, persister| persister.save(resource: m) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# (see Valkyrie::Persistence::Memory::Persister#save_all)
|
|
18
|
+
def save_all(resources:)
|
|
19
|
+
resources.map do |resource|
|
|
20
|
+
save(resource: resource)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# (see Valkyrie::Persistence::Memory::Persister#delete)
|
|
25
|
+
def delete(resource:)
|
|
26
|
+
persisters.inject(resource) { |m, persister| persister.delete(resource: m) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence
|
|
3
|
+
class DeleteTrackingBuffer < Valkyrie::Persistence::Memory::MetadataAdapter
|
|
4
|
+
def persister
|
|
5
|
+
@persister ||= DeleteTrackingBuffer::Persister.new(self)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Persister < Valkyrie::Persistence::Memory::Persister
|
|
9
|
+
attr_reader :deletes
|
|
10
|
+
def initialize(*args)
|
|
11
|
+
@deletes = []
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def delete(resource:)
|
|
16
|
+
@deletes << resource
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence
|
|
3
|
+
module Fedora
|
|
4
|
+
require 'valkyrie/persistence/fedora/metadata_adapter'
|
|
5
|
+
require 'valkyrie/persistence/fedora/persister'
|
|
6
|
+
require 'valkyrie/persistence/fedora/query_service'
|
|
7
|
+
require 'valkyrie/persistence/fedora/ordered_list'
|
|
8
|
+
require 'valkyrie/persistence/fedora/ordered_reader'
|
|
9
|
+
require 'valkyrie/persistence/fedora/list_node'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence::Fedora
|
|
3
|
+
class ListNode
|
|
4
|
+
attr_reader :rdf_subject, :graph
|
|
5
|
+
attr_accessor :prev, :next, :target
|
|
6
|
+
attr_writer :next_uri, :prev_uri
|
|
7
|
+
attr_accessor :proxy_in, :proxy_for
|
|
8
|
+
attr_reader :adapter
|
|
9
|
+
def initialize(node_cache, rdf_subject, adapter, graph = RDF::Repository.new)
|
|
10
|
+
@rdf_subject = rdf_subject
|
|
11
|
+
@graph = graph
|
|
12
|
+
@node_cache = node_cache
|
|
13
|
+
@adapter = adapter
|
|
14
|
+
Builder.new(rdf_subject, graph).populate(self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns the next proxy or a tail sentinel.
|
|
18
|
+
# @return [ActiveFedora::Orders::ListNode]
|
|
19
|
+
def next
|
|
20
|
+
@next ||=
|
|
21
|
+
if next_uri
|
|
22
|
+
node_cache.fetch(next_uri) do
|
|
23
|
+
node = self.class.new(node_cache, next_uri, adapter, graph)
|
|
24
|
+
node.prev = self
|
|
25
|
+
node
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the previous proxy or a head sentinel.
|
|
31
|
+
# @return [ActiveFedora::Orders::ListNode]
|
|
32
|
+
def prev
|
|
33
|
+
@prev ||= node_cache.fetch(prev_uri) if prev_uri
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Graph representation of node.
|
|
37
|
+
# @return [ActiveFedora::Orders::ListNode::Resource]
|
|
38
|
+
def to_graph
|
|
39
|
+
return RDF::Graph.new if target_id.blank?
|
|
40
|
+
g = Resource.new(rdf_subject)
|
|
41
|
+
g.proxy_for = target_uri
|
|
42
|
+
g.proxy_in = proxy_in.try(:uri)
|
|
43
|
+
g.next = self.next.try(:rdf_subject)
|
|
44
|
+
g.prev = prev.try(:rdf_subject)
|
|
45
|
+
g
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def target_uri
|
|
49
|
+
adapter.id_to_uri(target_id.to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def target_id
|
|
53
|
+
adapter.uri_to_id(proxy_for)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
attr_reader :next_uri, :prev_uri, :graph, :node_cache
|
|
59
|
+
|
|
60
|
+
class Builder
|
|
61
|
+
attr_reader :uri, :graph
|
|
62
|
+
def initialize(uri, graph)
|
|
63
|
+
@uri = uri
|
|
64
|
+
@graph = graph
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def populate(instance)
|
|
68
|
+
instance.proxy_for = resource.proxy_for.first
|
|
69
|
+
instance.proxy_in = resource.proxy_in.first
|
|
70
|
+
instance.next_uri = resource.next.first
|
|
71
|
+
instance.prev_uri = resource.prev.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def resource
|
|
77
|
+
@resource ||= Resource.new(uri, data: graph)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class Resource < ActiveTriples::Resource
|
|
82
|
+
property :proxy_for, predicate: ::RDF::Vocab::ORE.proxyFor, cast: false
|
|
83
|
+
property :proxy_in, predicate: ::RDF::Vocab::ORE.proxyIn, cast: false
|
|
84
|
+
property :next, predicate: ::RDF::Vocab::IANA.next, cast: false
|
|
85
|
+
property :prev, predicate: ::RDF::Vocab::IANA.prev, cast: false
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence::Fedora
|
|
3
|
+
class MetadataAdapter
|
|
4
|
+
attr_reader :connection, :base_path
|
|
5
|
+
def initialize(connection:, base_path: "/")
|
|
6
|
+
@connection = connection
|
|
7
|
+
@base_path = base_path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def query_service
|
|
11
|
+
Valkyrie::Persistence::Fedora::QueryService.new(adapter: self)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def persister
|
|
15
|
+
Valkyrie::Persistence::Fedora::Persister.new(adapter: self)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def resource_factory
|
|
19
|
+
Valkyrie::Persistence::Fedora::Persister::ResourceFactory.new(adapter: self)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def uri_to_id(uri)
|
|
23
|
+
Valkyrie::ID.new(uri.to_s.gsub(/^.*\//, ''))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def id_to_uri(id)
|
|
27
|
+
RDF::URI("#{connection_prefix}/#{pair_path(id)}/#{id}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def pair_path(id)
|
|
31
|
+
id.to_s.split("-").first.split("").each_slice(2).map(&:join).join("/")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def connection_prefix
|
|
35
|
+
"#{connection.options}/#{base_path}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wipe!
|
|
39
|
+
connection.delete(base_path)
|
|
40
|
+
connection.delete("#{base_path}/fcr:tombstone")
|
|
41
|
+
rescue => error
|
|
42
|
+
Valkyrie.logger.debug("Failed to wipe Fedora for some reason.") unless error.is_a?(::Ldp::NotFound)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Valkyrie::Persistence::Fedora
|
|
3
|
+
##
|
|
4
|
+
# Ruby object representation of an ORE doubly linked list.
|
|
5
|
+
class OrderedList
|
|
6
|
+
include Enumerable
|
|
7
|
+
attr_reader :graph, :head_subject, :tail_subject, :adapter
|
|
8
|
+
attr_writer :head, :tail
|
|
9
|
+
delegate :each, to: :ordered_reader
|
|
10
|
+
delegate :length, to: :to_a
|
|
11
|
+
# @param [::RDF::Enumerable] graph Enumerable where ORE statements are
|
|
12
|
+
# stored.
|
|
13
|
+
# @param [::RDF::URI] head_subject URI of head node in list.
|
|
14
|
+
# @param [::RDF::URI] tail_subject URI of tail node in list.
|
|
15
|
+
def initialize(graph, head_subject, tail_subject, adapter)
|
|
16
|
+
@graph = graph
|
|
17
|
+
@head_subject = head_subject
|
|
18
|
+
@tail_subject = tail_subject
|
|
19
|
+
@node_cache ||= NodeCache.new
|
|
20
|
+
@adapter = adapter
|
|
21
|
+
@changed = false
|
|
22
|
+
tail
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [HeadSentinel] Sentinel for the top of the list. If not empty,
|
|
26
|
+
# head.next is the first element.
|
|
27
|
+
def head
|
|
28
|
+
@head ||= HeadSentinel.new(self, next_node: build_node(head_subject))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [TailSentinel] Sentinel for the bottom of the list. If not
|
|
32
|
+
# empty, tail.prev is the first element.
|
|
33
|
+
def tail
|
|
34
|
+
@tail ||=
|
|
35
|
+
begin
|
|
36
|
+
if tail_subject
|
|
37
|
+
TailSentinel.new(self, prev_node: build_node(tail_subject))
|
|
38
|
+
else
|
|
39
|
+
head.next
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param [Integer] loc Location to insert target at
|
|
45
|
+
# @param [String] proxy_for proxyFor to add
|
|
46
|
+
def insert_proxy_for_at(loc, proxy_for, proxy_in: nil)
|
|
47
|
+
node = build_node(new_node_subject)
|
|
48
|
+
node.proxy_for = proxy_for
|
|
49
|
+
node.proxy_in = proxy_in
|
|
50
|
+
if loc.zero?
|
|
51
|
+
append_to(node, head)
|
|
52
|
+
else
|
|
53
|
+
append_to(node, ordered_reader.take(loc).last)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [::RDF::Graph] Graph representation of this list.
|
|
58
|
+
def to_graph
|
|
59
|
+
::RDF::Graph.new.tap do |g|
|
|
60
|
+
array = to_a
|
|
61
|
+
array.map(&:to_graph).each do |resource_graph|
|
|
62
|
+
g << resource_graph
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
attr_reader :node_cache
|
|
70
|
+
|
|
71
|
+
def append_to(source, append_node)
|
|
72
|
+
source.prev = append_node
|
|
73
|
+
append_node.next.prev = source
|
|
74
|
+
source.next = append_node.next
|
|
75
|
+
append_node.next = source
|
|
76
|
+
@changed = true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ordered_reader
|
|
80
|
+
OrderedReader.new(self)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_node(subject = nil)
|
|
84
|
+
return nil unless subject
|
|
85
|
+
node_cache.fetch(subject) do
|
|
86
|
+
ListNode.new(node_cache, subject, adapter, graph)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def new_node_subject
|
|
91
|
+
node = ::RDF::URI("##{::RDF::Node.new.id}")
|
|
92
|
+
node = ::RDF::URI("##{::RDF::Node.new.id}") while node_cache.key?(node)
|
|
93
|
+
node
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class NodeCache
|
|
97
|
+
def initialize
|
|
98
|
+
@cache ||= {}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def fetch(uri)
|
|
102
|
+
@cache[uri] ||= yield if block_given?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def key?(key)
|
|
106
|
+
@cache.key?(key)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class Sentinel
|
|
111
|
+
attr_reader :parent
|
|
112
|
+
attr_writer :next, :prev
|
|
113
|
+
def initialize(parent, next_node: nil, prev_node: nil)
|
|
114
|
+
@parent = parent
|
|
115
|
+
@next = next_node
|
|
116
|
+
@prev = prev_node
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
attr_reader :next
|
|
120
|
+
|
|
121
|
+
attr_reader :prev
|
|
122
|
+
|
|
123
|
+
def nil?
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def rdf_subject
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class HeadSentinel < Sentinel
|
|
133
|
+
def initialize(*args)
|
|
134
|
+
super
|
|
135
|
+
@next ||= TailSentinel.new(parent, prev_node: self)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class TailSentinel < Sentinel
|
|
140
|
+
def initialize(*args)
|
|
141
|
+
super
|
|
142
|
+
prev.next = self if prev && prev.next != self
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|