valkyrie 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|