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