needle 0.5.0 → 0.6.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.
- data/benchmarks/instantiability.rb +26 -0
- data/benchmarks/instantiation.rb +33 -0
- data/benchmarks/interceptors.rb +42 -0
- data/benchmarks/interceptors2.rb +70 -0
- data/doc/README +3 -1
- data/doc/di-in-ruby.rdoc +201 -0
- data/doc/images/di_classdiagram.jpg +0 -0
- data/doc/manual/manual.yml +4 -0
- data/doc/manual/parts/01_alternatives.txt +11 -0
- data/doc/manual/parts/02_creating.txt +20 -0
- data/doc/manual/parts/02_namespaces.txt +1 -1
- data/doc/manual/parts/02_services.txt +15 -3
- data/doc/manual-html/chapter-1.html +34 -7
- data/doc/manual-html/chapter-2.html +43 -9
- data/doc/manual-html/chapter-3.html +6 -4
- data/doc/manual-html/chapter-4.html +6 -4
- data/doc/manual-html/chapter-5.html +6 -4
- data/doc/manual-html/chapter-6.html +6 -4
- data/doc/manual-html/chapter-7.html +6 -4
- data/doc/manual-html/index.html +19 -4
- data/lib/needle/container.rb +104 -66
- data/{test/tc_models.rb → lib/needle/lifecycle/deferred.rb} +14 -20
- data/lib/needle/lifecycle/initialize.rb +49 -0
- data/lib/needle/{models → lifecycle}/proxy.rb +16 -8
- data/lib/needle/lifecycle/singleton.rb +63 -0
- data/lib/needle/lifecycle/threaded.rb +58 -0
- data/lib/needle/pipeline/collection.rb +133 -0
- data/lib/needle/pipeline/element.rb +85 -0
- data/lib/needle/pipeline/interceptor.rb +46 -0
- data/lib/needle/registry.rb +48 -8
- data/lib/needle/service-point.rb +36 -39
- data/lib/needle/thread.rb +87 -0
- data/lib/needle/version.rb +1 -1
- data/{lib/needle/models/prototype.rb → test/lifecycle/tc_deferred.rb} +15 -17
- data/test/lifecycle/tc_initialize.rb +62 -0
- data/test/{models → lifecycle}/tc_proxy.rb +5 -5
- data/test/lifecycle/tc_singleton.rb +32 -0
- data/{lib/needle/models/prototype-deferred.rb → test/lifecycle/tc_threaded.rb} +24 -18
- data/test/models/model_test.rb +131 -0
- data/test/models/tc_prototype.rb +9 -30
- data/test/models/tc_prototype_deferred.rb +9 -31
- data/test/models/tc_prototype_deferred_initialize.rb +32 -0
- data/test/models/tc_prototype_initialize.rb +32 -0
- data/test/models/tc_singleton.rb +8 -29
- data/test/models/tc_singleton_deferred.rb +8 -30
- data/test/models/tc_singleton_deferred_initialize.rb +32 -0
- data/test/models/tc_singleton_initialize.rb +32 -0
- data/test/models/tc_threaded.rb +32 -0
- data/test/models/tc_threaded_deferred.rb +32 -0
- data/test/models/tc_threaded_deferred_initialize.rb +32 -0
- data/test/models/tc_threaded_initialize.rb +32 -0
- data/test/pipeline/tc_collection.rb +116 -0
- data/test/pipeline/tc_element.rb +72 -0
- data/test/tc_container.rb +77 -36
- data/test/tc_logger.rb +5 -0
- data/test/tc_registry.rb +39 -1
- data/test/tc_service_point.rb +43 -7
- metadata +39 -12
- data/lib/needle/models/singleton-deferred.rb +0 -57
- data/lib/needle/models/singleton.rb +0 -56
- data/lib/needle/models.rb +0 -44
@@ -14,20 +14,28 @@
|
|
14
14
|
# =============================================================================
|
15
15
|
#++
|
16
16
|
|
17
|
-
require 'thread'
|
17
|
+
require 'needle/thread'
|
18
18
|
|
19
19
|
module Needle
|
20
|
-
module
|
20
|
+
module Lifecycle
|
21
21
|
|
22
22
|
# A proxy class to aid in deferred instantiation of service points. This is
|
23
23
|
# used primarily by the "deferred" service models.
|
24
24
|
class Proxy
|
25
25
|
|
26
|
-
# Create a new proxy that wraps the
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
# Create a new proxy object that wraps the object returned by either the
|
27
|
+
# +proc_obj+ or +callback+. (Only one of +proc_obj+ or +callback+ should
|
28
|
+
# be specified.)
|
29
|
+
def initialize( proc_obj=nil, *args, &callback )
|
30
|
+
if proc_obj && callback
|
31
|
+
raise ArgumentError, "only specify argument OR block, not both"
|
32
|
+
end
|
33
|
+
|
34
|
+
@callback = proc_obj || callback or
|
35
|
+
raise ArgumentError, "callback required"
|
36
|
+
|
37
|
+
@args = args
|
38
|
+
@mutex = QueryableMutex.new
|
31
39
|
@instantiation_failed = false
|
32
40
|
@instance = nil
|
33
41
|
end
|
@@ -39,7 +47,7 @@ module Needle
|
|
39
47
|
@mutex.synchronize do
|
40
48
|
unless @instance || @instantiation_failed
|
41
49
|
begin
|
42
|
-
@instance = @callback.call(
|
50
|
+
@instance = @callback.call( *@args )
|
43
51
|
rescue Exception
|
44
52
|
@instantiation_failed = true
|
45
53
|
raise
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the Needle dependency injection
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
|
10
|
+
# distribution for the texts of these licenses.
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# needle website : http://needle.rubyforge.org
|
13
|
+
# project website: http://rubyforge.org/projects/needle
|
14
|
+
# =============================================================================
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'needle/pipeline/element'
|
18
|
+
require 'needle/thread'
|
19
|
+
|
20
|
+
module Needle
|
21
|
+
module Lifecycle
|
22
|
+
|
23
|
+
# The instantiation pipeline element that enforces the singleton
|
24
|
+
# multiplicity.
|
25
|
+
class Singleton < Needle::Pipeline::Element
|
26
|
+
set_default_priority 100
|
27
|
+
|
28
|
+
# Creates the mutex to use and sets the cached reference to +nil+.
|
29
|
+
def initialize_element
|
30
|
+
@mutex = QueryableMutex.new
|
31
|
+
@cached = nil
|
32
|
+
@is_cached = false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the cached reference, if it has been previously cached.
|
36
|
+
# Otherwise, invokes the next element in the pipeline and caches the
|
37
|
+
# result. The cached reference is returned.
|
38
|
+
def call( *args )
|
39
|
+
unless @is_cached
|
40
|
+
@mutex.synchronize do
|
41
|
+
unless @is_cached
|
42
|
+
@cached = succ.call( *args )
|
43
|
+
@is_cached = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@cached
|
49
|
+
end
|
50
|
+
|
51
|
+
# Resets the cached singleton instance, so that the next time it is
|
52
|
+
# requested it is re-constructed.
|
53
|
+
def reset!
|
54
|
+
@mutex.synchronize do
|
55
|
+
@cached = nil
|
56
|
+
@is_cached = false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the Needle dependency injection
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
|
10
|
+
# distribution for the texts of these licenses.
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# needle website : http://needle.rubyforge.org
|
13
|
+
# project website: http://rubyforge.org/projects/needle
|
14
|
+
# =============================================================================
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'needle/pipeline/element'
|
18
|
+
|
19
|
+
module Needle
|
20
|
+
module Lifecycle
|
21
|
+
|
22
|
+
# The instantiation pipeline element that enforces the singleton
|
23
|
+
# multiplicity, on a per-thread basis.
|
24
|
+
class Threaded < Needle::Pipeline::Element
|
25
|
+
set_default_priority 100
|
26
|
+
|
27
|
+
# Returns a Hash of threaded services that are cached by the current
|
28
|
+
# thread.
|
29
|
+
def service_cache
|
30
|
+
Thread.current[ :threaded_services ] ||= Hash.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the cached reference, if it has been previously cached for
|
34
|
+
# the current thread. Otherwise, invokes the next element in the pipeline
|
35
|
+
# and caches the result. The cached reference is returned.
|
36
|
+
def call( *args )
|
37
|
+
cache = service_cache
|
38
|
+
name = service_point.fullname
|
39
|
+
|
40
|
+
unless cache.has_key?( name )
|
41
|
+
service = succ.call( *args )
|
42
|
+
cache[ name ] = service
|
43
|
+
end
|
44
|
+
|
45
|
+
cache[ name ]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resets the cached singleton instance, so that the next time it is
|
49
|
+
# requested it is re-constructed.
|
50
|
+
def reset!
|
51
|
+
cache = service_cache
|
52
|
+
cache.delete service_point.fullname
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the Needle dependency injection
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
|
10
|
+
# distribution for the texts of these licenses.
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# needle website : http://needle.rubyforge.org
|
13
|
+
# project website: http://rubyforge.org/projects/needle
|
14
|
+
# =============================================================================
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'needle/pipeline/element'
|
18
|
+
|
19
|
+
module Needle
|
20
|
+
module Pipeline
|
21
|
+
|
22
|
+
# Represents a collection of pipeline elements. Elements may be added to
|
23
|
+
# the pipeline, and then returned as a single object representing thechain
|
24
|
+
# of pipeline elements.
|
25
|
+
class Collection
|
26
|
+
|
27
|
+
# Wraps a block as a new pipeline element. When the element is invoked,
|
28
|
+
# control is delegated to the block.
|
29
|
+
class BlockElement < Element
|
30
|
+
|
31
|
+
set_default_priority 0
|
32
|
+
|
33
|
+
# Create a new pipeline element around the given block.
|
34
|
+
def initialize( point, name, priority, options, block )
|
35
|
+
super( point, name, priority, options )
|
36
|
+
@block = block
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invoke the block. The block must accept as many parameters as the
|
40
|
+
# pipeline expects, plus 1 (the first parameter is always this
|
41
|
+
# BlockElement instance).
|
42
|
+
def call( *args )
|
43
|
+
@block.call( self, *args )
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a new pipeline element collection, initially empty.
|
49
|
+
def initialize( service_point )
|
50
|
+
@elements = []
|
51
|
+
@service_point = service_point
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add a new pipeline element to this collection. If the first parameter
|
55
|
+
# is a symbol or a string, it is taken to be the name of the element to
|
56
|
+
# add. If no explicit implementation is given, then the implementation is
|
57
|
+
# looked up in the <tt>:pipeline_elements</tt> service, using the given
|
58
|
+
# name.
|
59
|
+
#
|
60
|
+
# After the first parameter, if the next parameter is numeric, it is taken
|
61
|
+
# to mean the priority of the element. This overrides whatever default
|
62
|
+
# priority is given for the selected element.
|
63
|
+
#
|
64
|
+
# If the next parameter responds to the <tt>:new</tt> message, it is taken
|
65
|
+
# to be an explicit implementation of the element. This is only valid if
|
66
|
+
# a block is not given. If a block is given, then it is always taken to be
|
67
|
+
# the implementation of the element.
|
68
|
+
#
|
69
|
+
# If the last parameter is a Hash, it is taken to be a map of options that
|
70
|
+
# should be passed to the element when it is instantiated.
|
71
|
+
#
|
72
|
+
# This returns +self+, so that #add calls may be chained.
|
73
|
+
def add( *args, &block )
|
74
|
+
name = priority = nil
|
75
|
+
options = {}
|
76
|
+
klass = nil
|
77
|
+
element = nil
|
78
|
+
|
79
|
+
if [ Symbol, String ].any? { |i| args.first.is_a?( i ) }
|
80
|
+
name = args.shift.to_s.intern
|
81
|
+
end
|
82
|
+
priority = args.shift if args.first.is_a?( Numeric )
|
83
|
+
klass = args.shift if args.first.respond_to?( :new ) && block.nil?
|
84
|
+
options = args.shift if args.first.is_a?( Hash )
|
85
|
+
|
86
|
+
raise ArgumentError,
|
87
|
+
"bad argument list #{args.inspect}" unless args.empty?
|
88
|
+
|
89
|
+
if block
|
90
|
+
element = BlockElement.new( @service_point, name, priority,
|
91
|
+
options, block )
|
92
|
+
else
|
93
|
+
klass ||= @service_point.container[:pipeline_elements].fetch( name )
|
94
|
+
element = klass.new( @service_point, name, priority, options )
|
95
|
+
end
|
96
|
+
|
97
|
+
@elements << element
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the first pipeline element with the given name, or +nil+ if no
|
102
|
+
# such element exists.
|
103
|
+
def get( name )
|
104
|
+
name = name.to_s.intern
|
105
|
+
@elements.find { |el| name == el.name }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Builds a linked list of the elements, with the last element being the
|
109
|
+
# block (or block-like) object given by the parameter. Higher-priority
|
110
|
+
# elements will be closer to the head of the list.
|
111
|
+
def chain_to( block )
|
112
|
+
head = tail = nil
|
113
|
+
@elements.sort.reverse.each do |el|
|
114
|
+
if head
|
115
|
+
tail.succ = el
|
116
|
+
tail = el
|
117
|
+
else
|
118
|
+
head = tail = el
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if tail
|
123
|
+
tail.succ = block
|
124
|
+
return head
|
125
|
+
else
|
126
|
+
return block
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the Needle dependency injection
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
|
10
|
+
# distribution for the texts of these licenses.
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# needle website : http://needle.rubyforge.org
|
13
|
+
# project website: http://rubyforge.org/projects/needle
|
14
|
+
# =============================================================================
|
15
|
+
#++
|
16
|
+
|
17
|
+
module Needle
|
18
|
+
module Pipeline
|
19
|
+
|
20
|
+
# The base class of instantiation pipeline elements. All subclasses
|
21
|
+
# MUST implement is the #call method, to define the logic that instances of
|
22
|
+
# that pipeline element should perform when invoked.
|
23
|
+
class Element
|
24
|
+
include Comparable
|
25
|
+
|
26
|
+
# The service definition that this element belongs to.
|
27
|
+
attr_reader :service_point
|
28
|
+
|
29
|
+
# The name of this element (may be +nil+).
|
30
|
+
attr_reader :name
|
31
|
+
|
32
|
+
# The priority of this element, used to determine ordering. Higher ordered
|
33
|
+
# elements are invoked before lower-ordered elements.
|
34
|
+
attr_reader :priority
|
35
|
+
|
36
|
+
# The hash of options that were given to this element.
|
37
|
+
attr_reader :options
|
38
|
+
|
39
|
+
# The next element in the chain. This value is only valid during pipeline
|
40
|
+
# execution--its value should not be relied upon at any other time.
|
41
|
+
attr_accessor :succ
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# The default priority to use for elements of this type.
|
45
|
+
attr_reader :default_priority
|
46
|
+
|
47
|
+
# Set the default priority for elements of this type. Subclasses may
|
48
|
+
# use this method to set their default priority.
|
49
|
+
def set_default_priority( priority )
|
50
|
+
@default_priority = priority
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
set_default_priority 0
|
55
|
+
|
56
|
+
# Create a new element instance with the given name and priority. This
|
57
|
+
# will call #initialize_element, so that subclasses only need to
|
58
|
+
# implement that method if they have any initialization logic to perform.
|
59
|
+
def initialize( point, name=nil, priority=nil, options={} )
|
60
|
+
@service_point = point
|
61
|
+
@name, @priority = name, ( priority || self.class.default_priority )
|
62
|
+
@options = options
|
63
|
+
initialize_element
|
64
|
+
end
|
65
|
+
|
66
|
+
# Invoked by the constructor to perform any subclass-specific
|
67
|
+
# initialization logic.
|
68
|
+
def initialize_element
|
69
|
+
end
|
70
|
+
|
71
|
+
# Orders elements by their priority.
|
72
|
+
def <=>( element )
|
73
|
+
priority <=> element.priority
|
74
|
+
end
|
75
|
+
|
76
|
+
# Invoke this element's logic.
|
77
|
+
def call( *args )
|
78
|
+
raise NotImplementedError
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :[] :call
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the Needle dependency injection
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
|
10
|
+
# distribution for the texts of these licenses.
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# needle website : http://needle.rubyforge.org
|
13
|
+
# project website: http://rubyforge.org/projects/needle
|
14
|
+
# =============================================================================
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'needle/interceptor-chain'
|
18
|
+
require 'needle/pipeline/element'
|
19
|
+
|
20
|
+
module Needle
|
21
|
+
module Pipeline
|
22
|
+
|
23
|
+
# The pipeline element that implements adding interceptors to a service.
|
24
|
+
class InterceptorElement < Element
|
25
|
+
|
26
|
+
set_default_priority 0
|
27
|
+
|
28
|
+
# The array of interceptors to be proxied onto the object.
|
29
|
+
attr_reader :interceptors
|
30
|
+
|
31
|
+
# Initializes the array of interceptors. It is initially empty.
|
32
|
+
def initialize_element
|
33
|
+
@interceptors = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Invokes the next element in the chain and adds the interceptors to
|
37
|
+
# the result, returning a proxy object wrapped by the interceptors.
|
38
|
+
def call( container, point, *args )
|
39
|
+
service = succ.call( container, point, *args )
|
40
|
+
InterceptorChainBuilder.build( point, service, @interceptors )
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/needle/registry.rb
CHANGED
@@ -16,9 +16,13 @@
|
|
16
16
|
|
17
17
|
require 'needle/container'
|
18
18
|
require 'needle/errors'
|
19
|
+
require 'needle/lifecycle/deferred'
|
20
|
+
require 'needle/lifecycle/initialize'
|
21
|
+
require 'needle/lifecycle/singleton'
|
22
|
+
require 'needle/lifecycle/threaded'
|
19
23
|
require 'needle/log-factory'
|
20
24
|
require 'needle/logging-interceptor'
|
21
|
-
require 'needle/
|
25
|
+
require 'needle/pipeline/interceptor'
|
22
26
|
|
23
27
|
module Needle
|
24
28
|
|
@@ -42,7 +46,7 @@ module Needle
|
|
42
46
|
# each names. By default, the name is empty.
|
43
47
|
DEFAULT_REGISTRY_NAME = ""
|
44
48
|
|
45
|
-
# Instantiate a new Registry (via #new) and immediately invoke #
|
49
|
+
# Instantiate a new Registry (via #new) and immediately invoke #define!
|
46
50
|
# using the given block.
|
47
51
|
#
|
48
52
|
# Usage:
|
@@ -55,13 +59,16 @@ module Needle
|
|
55
59
|
# adder = registry.add
|
56
60
|
def self.new!( *parms, &block )
|
57
61
|
raise NeedleError, "needs a block" if block.nil?
|
58
|
-
new( *parms ) { |reg| reg.
|
62
|
+
new( *parms ) { |reg| reg.define!( &block ) }
|
59
63
|
end
|
60
64
|
|
61
65
|
# Instantiate a new Registry. If the first parameter is a string, it will
|
62
66
|
# be used as the name of this registry. If the next parameter is a Hash,
|
63
67
|
# it will be used as the options to use when bootstrapping the registry.
|
64
|
-
#
|
68
|
+
#
|
69
|
+
# The options hash may include options used to initialize the logger
|
70
|
+
# factory. The logger factory options should be in another hash, keyed
|
71
|
+
# by the value <tt>:logs</tt>.
|
65
72
|
#
|
66
73
|
# If a block is given, the constructed registry instance is yielded to it.
|
67
74
|
#
|
@@ -74,6 +81,12 @@ module Needle
|
|
74
81
|
# registry = Needle::Registry.new do |reg|
|
75
82
|
# reg.register( :add ) { Adder.new }
|
76
83
|
# end
|
84
|
+
#
|
85
|
+
# or
|
86
|
+
#
|
87
|
+
# registry = Needle::Registry.new(
|
88
|
+
# :logs => { :filename => "/dev/null" }
|
89
|
+
# )
|
77
90
|
def initialize( *parms )
|
78
91
|
raise ArgumentError, "expected <= 2 arguments" if parms.length > 2
|
79
92
|
name = ( parms.first.is_a?( String ) ? parms.shift :
|
@@ -95,11 +108,38 @@ module Needle
|
|
95
108
|
"[#{super}]"
|
96
109
|
end
|
97
110
|
|
98
|
-
# Bootstraps the service models, logger factory, and
|
99
|
-
# services into the current registry. This is only
|
100
|
-
# registry is created.
|
111
|
+
# Bootstraps the pipeline elements, service models, logger factory, and
|
112
|
+
# logging interceptor services into the current registry. This is only
|
113
|
+
# called when a new registry is created.
|
101
114
|
def bootstrap( opts )
|
102
|
-
|
115
|
+
register( :pipeline_elements, :pipeline=>[] ) { Hash.new }
|
116
|
+
pipeline( :pipeline_elements ).add( :singleton,
|
117
|
+
Needle::Lifecycle::Singleton )
|
118
|
+
|
119
|
+
self[:pipeline_elements].update(
|
120
|
+
:singleton => Needle::Lifecycle::Singleton,
|
121
|
+
:initialize => Needle::Lifecycle::Initialize,
|
122
|
+
:deferred => Needle::Lifecycle::Deferred,
|
123
|
+
:interceptor => Needle::Pipeline::InterceptorElement,
|
124
|
+
:threaded => Needle::Lifecycle::Threaded
|
125
|
+
)
|
126
|
+
|
127
|
+
register( :service_models, :pipeline=>[:singleton] ) { Hash.new }
|
128
|
+
self[:service_models].update(
|
129
|
+
:prototype => [],
|
130
|
+
:prototype_initialize => [ :initialize ],
|
131
|
+
:prototype_deferred => [ :deferred ],
|
132
|
+
:prototype_deferred_initialize => [ :deferred, :initialize ],
|
133
|
+
:singleton => [ :singleton ],
|
134
|
+
:singleton_initialize => [ :singleton, :initialize ],
|
135
|
+
:singleton_deferred => [ :singleton, :deferred ],
|
136
|
+
:singleton_deferred_initialize => [ :singleton, :deferred, :initialize ],
|
137
|
+
:threaded => [ :threaded ],
|
138
|
+
:threaded_initialize => [ :threaded, :initialize ],
|
139
|
+
:threaded_deferred => [ :threaded, :deferred ],
|
140
|
+
:threaded_deferred_initialize => [ :threaded, :deferred, :initialize ]
|
141
|
+
)
|
142
|
+
|
103
143
|
register( :logs ) { LogFactory.new( opts[:logs] || {} ) }
|
104
144
|
register( :logging_interceptor ) { LoggingInterceptor }
|
105
145
|
end
|
data/lib/needle/service-point.rb
CHANGED
@@ -14,7 +14,8 @@
|
|
14
14
|
# =============================================================================
|
15
15
|
#++
|
16
16
|
|
17
|
-
require 'needle/
|
17
|
+
require 'needle/pipeline/collection'
|
18
|
+
require 'needle/thread'
|
18
19
|
|
19
20
|
module Needle
|
20
21
|
|
@@ -23,7 +24,7 @@ module Needle
|
|
23
24
|
# particular, a service point also knows how to instantiate a service.
|
24
25
|
#
|
25
26
|
# A ServicePoint should never be directly instantiated. Instead, define
|
26
|
-
# services via the
|
27
|
+
# services via the interfaces provided by Container.
|
27
28
|
class ServicePoint
|
28
29
|
|
29
30
|
# The name of this service point, as it is known to the container that it
|
@@ -33,40 +34,39 @@ module Needle
|
|
33
34
|
# A reference to the container that contains this service point.
|
34
35
|
attr_reader :container
|
35
36
|
|
37
|
+
# The reference to the instantiation pipeline used by this service point.
|
38
|
+
attr_reader :pipeline
|
39
|
+
|
36
40
|
# Create a new service point that references the given container and has
|
37
41
|
# the given name. The associated callback will be used to instantiate the
|
38
42
|
# service on demand.
|
39
43
|
#
|
40
44
|
# The <tt>:model</tt> option is used to tell Needle which style of
|
41
45
|
# life-cycle management should be used for the service. It defaults to
|
42
|
-
# <tt>:singleton</tt>. The model
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
46
|
+
# <tt>:singleton</tt>. The model must be a symbol that refers to a service
|
47
|
+
# model that has been registered in the root <tt>:service_models</tt>
|
48
|
+
# service.
|
49
|
+
#
|
50
|
+
# The <tt>:pipeline</tt> option is mutually exclusive with <tt>:model</tt>.
|
51
|
+
# It must be an array of symbols (or strings) that define the instantiation
|
52
|
+
# pipeline to use for this service. Each element must correspond to an entry
|
53
|
+
# in the <tt>:pipeline_elements</tt> service.
|
48
54
|
def initialize( container, name, opts={}, &callback )
|
49
55
|
@name = name
|
50
56
|
@container = container
|
51
57
|
@callback = callback
|
52
|
-
@
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
when String then
|
62
|
-
model = model.intern
|
63
|
-
model_factory = @container.root.service_models[model]
|
64
|
-
else
|
65
|
-
model = model.to_s.intern
|
66
|
-
model_factory = @container.root.service_models[model]
|
58
|
+
@pipeline = Needle::Pipeline::Collection.new self
|
59
|
+
@chain = nil
|
60
|
+
@mutex = QueryableMutex.new
|
61
|
+
|
62
|
+
if opts[:pipeline]
|
63
|
+
elements = opts[:pipeline]
|
64
|
+
else
|
65
|
+
model = opts[:model] || :singleton
|
66
|
+
elements = @container[:service_models][model]
|
67
67
|
end
|
68
68
|
|
69
|
-
|
69
|
+
elements.each { |element| @pipeline.add element, opts }
|
70
70
|
end
|
71
71
|
|
72
72
|
# Returns the fully-qualified name of the service point, with the point's
|
@@ -79,30 +79,27 @@ module Needle
|
|
79
79
|
# Adds the given interceptor definition to this service point. The
|
80
80
|
# parameter should act like an instance of Interceptor.
|
81
81
|
def interceptor( interceptor )
|
82
|
-
@
|
83
|
-
|
82
|
+
element = @pipeline.get( :interceptor )
|
83
|
+
unless element
|
84
|
+
@chain = nil
|
85
|
+
@pipeline.add( :interceptor )
|
86
|
+
element = @pipeline.get( :interceptor )
|
87
|
+
end
|
88
|
+
element.interceptors << interceptor
|
84
89
|
end
|
85
90
|
|
86
91
|
# Return the service instance represented by this service point. Depending
|
87
92
|
# on the style of lifecycle management chosen for this service point, this
|
88
93
|
# may or may not be a new instance for every invocation of this method.
|
89
94
|
def instance
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# callback that was registered for this service point, installs any
|
95
|
-
# requested interceptors, and returns the new instance.
|
96
|
-
def instantiate
|
97
|
-
instance = @callback.call( @container )
|
98
|
-
|
99
|
-
if @interceptors
|
100
|
-
instance = InterceptorChainBuilder.build( self, instance, @interceptors )
|
95
|
+
unless @chain
|
96
|
+
@mutex.synchronize do
|
97
|
+
@chain = @pipeline.chain_to( @callback ) unless @chain
|
98
|
+
end
|
101
99
|
end
|
102
100
|
|
103
|
-
|
101
|
+
@chain.call( @container, self )
|
104
102
|
end
|
105
|
-
private :instantiate
|
106
103
|
|
107
104
|
end
|
108
105
|
|