needle 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/benchmarks/instantiation.rb +39 -0
- data/doc/faq/faq.yml +137 -102
- data/doc/manual-html/chapter-1.html +15 -19
- data/doc/manual-html/chapter-2.html +52 -23
- data/doc/manual-html/chapter-3.html +7 -9
- data/doc/manual-html/chapter-4.html +6 -8
- data/doc/manual-html/chapter-5.html +16 -19
- data/doc/manual-html/chapter-6.html +31 -8
- data/doc/manual-html/chapter-7.html +19 -8
- data/doc/manual-html/chapter-8.html +8 -11
- data/doc/manual-html/chapter-9.html +7 -6
- data/doc/manual-html/index.html +5 -3
- data/doc/manual/manual.yml +2 -1
- data/doc/manual/parts/01_alternatives.txt +2 -1
- data/doc/manual/parts/02_services.txt +33 -0
- data/doc/manual/parts/logging_logfactory.txt +9 -0
- data/doc/manual/parts/models_models.txt +4 -0
- data/doc/manual/parts/models_pipelines.txt +1 -0
- data/lib/needle/container.rb +64 -19
- data/lib/needle/definition-context.rb +34 -6
- data/lib/needle/lifecycle/multiton.rb +64 -0
- data/lib/needle/lifecycle/singleton.rb +2 -2
- data/lib/needle/lifecycle/threaded.rb +4 -3
- data/lib/needle/pipeline/collection.rb +10 -1
- data/lib/needle/pipeline/element.rb +6 -0
- data/lib/needle/registry.rb +18 -5
- data/lib/needle/service-point.rb +21 -10
- data/lib/needle/version.rb +1 -1
- data/test/lifecycle/tc_multiton.rb +43 -0
- data/test/lifecycle/tc_singleton.rb +18 -2
- data/test/lifecycle/tc_threaded.rb +12 -6
- data/test/pipeline/tc_collection.rb +26 -2
- data/test/services.rb +8 -0
- data/test/tc_container.rb +82 -0
- data/test/tc_definition_context.rb +63 -7
- data/test/tc_registry.rb +13 -1
- data/test/tc_service_point.rb +49 -0
- metadata +4 -2
@@ -35,11 +35,11 @@ module Needle
|
|
35
35
|
# Returns the cached reference, if it has been previously cached.
|
36
36
|
# Otherwise, invokes the next element in the pipeline and caches the
|
37
37
|
# result. The cached reference is returned.
|
38
|
-
def call(
|
38
|
+
def call( container, point )
|
39
39
|
unless @is_cached
|
40
40
|
@mutex.synchronize do
|
41
41
|
unless @is_cached
|
42
|
-
@cached = succ.call(
|
42
|
+
@cached = succ.call( container, point )
|
43
43
|
@is_cached = true
|
44
44
|
end
|
45
45
|
end
|
@@ -33,12 +33,12 @@ module Needle
|
|
33
33
|
# Returns the cached reference, if it has been previously cached for
|
34
34
|
# the current thread. Otherwise, invokes the next element in the pipeline
|
35
35
|
# and caches the result. The cached reference is returned.
|
36
|
-
def call(
|
36
|
+
def call( container, point )
|
37
37
|
cache = service_cache
|
38
38
|
name = service_point.fullname
|
39
39
|
|
40
40
|
unless cache.has_key?( name )
|
41
|
-
service = succ.call(
|
41
|
+
service = succ.call( container, point )
|
42
42
|
cache[ name ] = service
|
43
43
|
end
|
44
44
|
|
@@ -46,7 +46,8 @@ module Needle
|
|
46
46
|
end
|
47
47
|
|
48
48
|
# Resets the cached singleton instance, so that the next time it is
|
49
|
-
# requested it is re-constructed.
|
49
|
+
# requested it is re-constructed. Only the cache for the current thread
|
50
|
+
# and service point is reset.
|
50
51
|
def reset!
|
51
52
|
cache = service_cache
|
52
53
|
cache.delete service_point.fullname
|
@@ -20,7 +20,7 @@ module Needle
|
|
20
20
|
module Pipeline
|
21
21
|
|
22
22
|
# Represents a collection of pipeline elements. Elements may be added to
|
23
|
-
# the pipeline, and then returned as a single object representing
|
23
|
+
# the pipeline, and then returned as a single object representing the chain
|
24
24
|
# of pipeline elements.
|
25
25
|
class Collection
|
26
26
|
|
@@ -105,6 +105,15 @@ module Needle
|
|
105
105
|
@elements.find { |el| name == el.name }
|
106
106
|
end
|
107
107
|
|
108
|
+
# Clears the state for each named element. If no elements are named, all
|
109
|
+
# elements are cleared.
|
110
|
+
def reset!( *args )
|
111
|
+
@elements.each do |element|
|
112
|
+
element.reset! if args.empty? || args.include?( element.name )
|
113
|
+
end
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
108
117
|
# Builds a linked list of the elements, with the last element being the
|
109
118
|
# block (or block-like) object given by the parameter. Higher-priority
|
110
119
|
# elements will be closer to the head of the list.
|
@@ -78,6 +78,12 @@ module Needle
|
|
78
78
|
raise NotImplementedError
|
79
79
|
end
|
80
80
|
|
81
|
+
# Invoked by Pipeline::Collection#reset!. Subclasses of Element that
|
82
|
+
# save any kind of state should override this method to clear that
|
83
|
+
# state on demand.
|
84
|
+
def reset!
|
85
|
+
end
|
86
|
+
|
81
87
|
alias :[] :call
|
82
88
|
end
|
83
89
|
|
data/lib/needle/registry.rb
CHANGED
@@ -20,6 +20,7 @@ require 'needle/errors'
|
|
20
20
|
require 'needle/interceptor'
|
21
21
|
require 'needle/lifecycle/deferred'
|
22
22
|
require 'needle/lifecycle/initialize'
|
23
|
+
require 'needle/lifecycle/multiton'
|
23
24
|
require 'needle/lifecycle/singleton'
|
24
25
|
require 'needle/lifecycle/threaded'
|
25
26
|
require 'needle/log-factory'
|
@@ -125,11 +126,16 @@ module Needle
|
|
125
126
|
:initialize => Needle::Lifecycle::Initialize,
|
126
127
|
:deferred => Needle::Lifecycle::Deferred,
|
127
128
|
:interceptor => Needle::Pipeline::InterceptorElement,
|
129
|
+
:multiton => Needle::Lifecycle::Multiton,
|
128
130
|
:threaded => Needle::Lifecycle::Threaded
|
129
131
|
)
|
130
132
|
|
131
133
|
register( :service_models, :pipeline=>[:singleton] ) { Hash.new }
|
132
134
|
self[:service_models].update(
|
135
|
+
:multiton => [ :multiton ],
|
136
|
+
:multiton_initialize => [ :multiton, :initialize ],
|
137
|
+
:multiton_deferred => [ :multiton, :deferred ],
|
138
|
+
:multiton_deferred_initialize => [ :multiton, :deferred, :initialize ],
|
133
139
|
:prototype => [],
|
134
140
|
:prototype_initialize => [ :initialize ],
|
135
141
|
:prototype_deferred => [ :deferred ],
|
@@ -137,7 +143,7 @@ module Needle
|
|
137
143
|
:singleton => [ :singleton ],
|
138
144
|
:singleton_initialize => [ :singleton, :initialize ],
|
139
145
|
:singleton_deferred => [ :singleton, :deferred ],
|
140
|
-
:singleton_deferred_initialize => [ :singleton, :deferred, :initialize
|
146
|
+
:singleton_deferred_initialize => [ :singleton, :deferred, :initialize],
|
141
147
|
:threaded => [ :threaded ],
|
142
148
|
:threaded_initialize => [ :threaded, :initialize ],
|
143
149
|
:threaded_deferred => [ :threaded, :deferred ],
|
@@ -145,11 +151,18 @@ module Needle
|
|
145
151
|
)
|
146
152
|
|
147
153
|
register( :definition_context_factory ) { DefinitionContext }
|
148
|
-
register( :namespace_impl_factory ) { Container }
|
149
|
-
register( :interceptor_impl_factory ) { Interceptor }
|
150
154
|
|
151
|
-
|
152
|
-
|
155
|
+
define do |b|
|
156
|
+
b.namespace_impl_factory { Container }
|
157
|
+
b.interceptor_impl_factory { Interceptor }
|
158
|
+
|
159
|
+
b.logs { LogFactory.new( opts[:logs] || {} ) }
|
160
|
+
|
161
|
+
# a parameterized version of c[:logs] for easier access
|
162
|
+
b.log_for( :model => :prototype ) { |c,p,name| c[:logs].get(name) }
|
163
|
+
|
164
|
+
b.logging_interceptor { LoggingInterceptor }
|
165
|
+
end
|
153
166
|
end
|
154
167
|
private :bootstrap
|
155
168
|
|
data/lib/needle/service-point.rb
CHANGED
@@ -20,7 +20,7 @@ require 'needle/thread'
|
|
20
20
|
module Needle
|
21
21
|
|
22
22
|
# A "service point" is a definition of a service. Just as a class defines the
|
23
|
-
# behavior of an object, so does a service point
|
23
|
+
# behavior of an object, so does a service point describe a service. In
|
24
24
|
# particular, a service point also knows how to instantiate a service.
|
25
25
|
#
|
26
26
|
# A ServicePoint should never be directly instantiated. Instead, define
|
@@ -57,7 +57,9 @@ module Needle
|
|
57
57
|
@callback = callback
|
58
58
|
@pipeline = Needle::Pipeline::Collection.new self
|
59
59
|
@chain = nil
|
60
|
-
|
60
|
+
|
61
|
+
@chain_mutex = QueryableMutex.new
|
62
|
+
@element_mutex = QueryableMutex.new
|
61
63
|
|
62
64
|
if opts[:pipeline]
|
63
65
|
elements = opts[:pipeline]
|
@@ -66,6 +68,7 @@ module Needle
|
|
66
68
|
elements = @container[:service_models][model]
|
67
69
|
end
|
68
70
|
|
71
|
+
elements.concat [ *opts[:include] ] if opts[:include]
|
69
72
|
elements.each { |element| @pipeline.add element, opts }
|
70
73
|
end
|
71
74
|
|
@@ -84,26 +87,34 @@ module Needle
|
|
84
87
|
# Adds the given interceptor definition to this service point. The
|
85
88
|
# parameter should act like an instance of Interceptor.
|
86
89
|
def interceptor( interceptor )
|
87
|
-
|
88
|
-
unless element
|
89
|
-
@chain = nil
|
90
|
-
@pipeline.add( :interceptor )
|
90
|
+
@element_mutex.synchronize do
|
91
91
|
element = @pipeline.get( :interceptor )
|
92
|
+
unless element
|
93
|
+
@pipeline.add( :interceptor )
|
94
|
+
element = @pipeline.get( :interceptor )
|
95
|
+
end
|
96
|
+
element.interceptors << interceptor
|
97
|
+
@pipeline.reset!
|
98
|
+
@chain = nil
|
92
99
|
end
|
93
|
-
element.interceptors << interceptor
|
94
100
|
end
|
95
101
|
|
96
102
|
# Return the service instance represented by this service point. Depending
|
97
103
|
# on the style of lifecycle management chosen for this service point, this
|
98
104
|
# may or may not be a new instance for every invocation of this method.
|
99
|
-
|
105
|
+
#
|
106
|
+
# Any arguments to this method will be passed through to the chain, which
|
107
|
+
# may cause an error if there is an element in the pipeline that does not
|
108
|
+
# accept additional arguments. Regardless, the first two parameters to
|
109
|
+
# the chain will always be the container and the service point.
|
110
|
+
def instance( *args )
|
100
111
|
unless @chain
|
101
|
-
@
|
112
|
+
@chain_mutex.synchronize do
|
102
113
|
@chain = @pipeline.chain_to( @callback ) unless @chain
|
103
114
|
end
|
104
115
|
end
|
105
116
|
|
106
|
-
@chain.call( @container, self )
|
117
|
+
@chain.call( @container, self, *args )
|
107
118
|
end
|
108
119
|
|
109
120
|
end
|
data/lib/needle/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
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
|
+
$:.unshift "../../lib"
|
18
|
+
|
19
|
+
require "needle/lifecycle/multiton"
|
20
|
+
require "test/unit"
|
21
|
+
|
22
|
+
class TC_Lifecycle_Multiton < Test::Unit::TestCase
|
23
|
+
|
24
|
+
def test_multiplicity
|
25
|
+
element = Needle::Lifecycle::Multiton.new( nil )
|
26
|
+
element.succ = Proc.new { |c,p,x| "value: #{x}" }
|
27
|
+
p1 = element.call( nil, nil, :v )
|
28
|
+
p2 = element.call( nil, nil, :v )
|
29
|
+
assert_same p1, p2
|
30
|
+
p3 = element.call( nil, nil, :x )
|
31
|
+
assert_not_same p1, p3
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_reset!
|
35
|
+
element = Needle::Lifecycle::Multiton.new( nil )
|
36
|
+
element.succ = Proc.new { |c,p,x| "value: #{x}" }
|
37
|
+
p1 = element.call( nil, nil, :v )
|
38
|
+
element.reset!
|
39
|
+
p2 = element.call( nil, nil, :v )
|
40
|
+
assert_not_same p1, p2
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -24,9 +24,25 @@ class TC_Lifecycle_Singleton < Test::Unit::TestCase
|
|
24
24
|
def test_multiplicity
|
25
25
|
element = Needle::Lifecycle::Singleton.new( nil )
|
26
26
|
element.succ = Proc.new { Hash.new }
|
27
|
-
p1 = element.call
|
28
|
-
p2 = element.call
|
27
|
+
p1 = element.call( nil, nil )
|
28
|
+
p2 = element.call( nil, nil )
|
29
29
|
assert_same p1, p2
|
30
30
|
end
|
31
31
|
|
32
|
+
def test_extra_args_disallowed
|
33
|
+
element = Needle::Lifecycle::Singleton.new( nil )
|
34
|
+
assert_raise( ArgumentError ) do
|
35
|
+
element.call( 1, 2, 3 )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_reset!
|
40
|
+
element = Needle::Lifecycle::Singleton.new( nil )
|
41
|
+
element.succ = Proc.new { Hash.new }
|
42
|
+
p1 = element.call( nil, nil )
|
43
|
+
element.reset!
|
44
|
+
p2 = element.call( nil, nil )
|
45
|
+
assert_not_same p1, p2
|
46
|
+
end
|
47
|
+
|
32
48
|
end
|
@@ -29,19 +29,25 @@ class TC_Lifecycle_Threaded < Test::Unit::TestCase
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_single_thread
|
32
|
-
@element.call
|
33
|
-
@element.call
|
34
|
-
@element.call
|
32
|
+
@element.call(1,2)
|
33
|
+
@element.call(1,2)
|
34
|
+
@element.call(1,2)
|
35
35
|
assert_equal 1, @counter
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_multi_thread
|
39
39
|
threads = []
|
40
|
-
threads << Thread.new { @element.call }
|
41
|
-
threads << Thread.new { @element.call }
|
42
|
-
threads << Thread.new { @element.call }
|
40
|
+
threads << Thread.new { @element.call(1,2) }
|
41
|
+
threads << Thread.new { @element.call(1,2) }
|
42
|
+
threads << Thread.new { @element.call(1,2) }
|
43
43
|
threads.each { |t| t.join }
|
44
44
|
assert_equal 3, @counter
|
45
45
|
end
|
46
46
|
|
47
|
+
def test_extra_args_disallowed
|
48
|
+
assert_raise( ArgumentError ) do
|
49
|
+
@element.call(1,2,3)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
47
53
|
end
|
@@ -27,9 +27,12 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
|
|
27
27
|
attr_reader :priority
|
28
28
|
attr_accessor :succ
|
29
29
|
|
30
|
+
attr_reader :events
|
31
|
+
|
30
32
|
def initialize( point, name, priority, options )
|
31
33
|
@service_point = point
|
32
34
|
@name, @priority = name, priority
|
35
|
+
@events = []
|
33
36
|
end
|
34
37
|
|
35
38
|
def call( *args )
|
@@ -41,11 +44,16 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
|
|
41
44
|
def <=>( element )
|
42
45
|
priority <=> element.priority
|
43
46
|
end
|
47
|
+
|
48
|
+
def reset!
|
49
|
+
@events << :reset!
|
50
|
+
end
|
44
51
|
end
|
45
52
|
|
46
53
|
def setup
|
47
|
-
elements =
|
48
|
-
|
54
|
+
elements = Struct.new( :fullname, :pipeline_elements ).new(
|
55
|
+
"test", { :mock => MockElement, :mock2 => MockElement } )
|
56
|
+
point = Struct.new( :container, :fullname ).new( elements, "test" )
|
49
57
|
@collection = Needle::Pipeline::Collection.new( point )
|
50
58
|
end
|
51
59
|
|
@@ -113,4 +121,20 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
|
|
113
121
|
assert_equal b, "|proc|Mock|tail|Done|end"
|
114
122
|
end
|
115
123
|
|
124
|
+
def test_reset_one!
|
125
|
+
@collection.add :mock, 25
|
126
|
+
@collection.add :mock2, 25
|
127
|
+
@collection.reset! :mock2
|
128
|
+
assert_equal [ :reset! ], @collection.get( :mock2 ).events
|
129
|
+
assert_equal [], @collection.get( :mock ).events
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_reset!
|
133
|
+
@collection.add :mock, 25
|
134
|
+
@collection.add :mock2, 25
|
135
|
+
@collection.reset!
|
136
|
+
assert_equal [ :reset! ], @collection.get( :mock2 ).events
|
137
|
+
assert_equal [ :reset! ], @collection.get( :mock ).events
|
138
|
+
end
|
139
|
+
|
116
140
|
end
|
data/test/services.rb
CHANGED
@@ -16,6 +16,14 @@ module A
|
|
16
16
|
end
|
17
17
|
module_function :register_other_services
|
18
18
|
|
19
|
+
def register_parameterized_services( container )
|
20
|
+
container.define do |b|
|
21
|
+
b.baz1 { |c,p,*args| args.join(":") }
|
22
|
+
b.baz2( :model=>:prototype ) { |c,p,*a| a.join(":") }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
module_function :register_parameterized_services
|
26
|
+
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
data/test/tc_container.rb
CHANGED
@@ -370,4 +370,86 @@ class TC_Container < Test::Unit::TestCase
|
|
370
370
|
assert_equal CustomInterceptor, subspace.intercept( :logs ).class
|
371
371
|
end
|
372
372
|
|
373
|
+
def test_descended_from?
|
374
|
+
outer = new_container
|
375
|
+
middle = new_container( outer )
|
376
|
+
inner = new_container( middle )
|
377
|
+
assert inner.descended_from?( outer )
|
378
|
+
assert inner.descended_from?( middle )
|
379
|
+
assert inner.descended_from?( inner )
|
380
|
+
assert !outer.descended_from?( inner )
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_defaults_inherited
|
384
|
+
root = new_container
|
385
|
+
root.defaults[:hello] = :world
|
386
|
+
c1 = new_container( root )
|
387
|
+
assert_equal :world, c1.defaults[:hello]
|
388
|
+
root.defaults[:hello] = :jisang
|
389
|
+
assert_equal :world, c1.defaults[:hello]
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_defaults_used
|
393
|
+
root = new_container
|
394
|
+
root.define.foo { Object.new }
|
395
|
+
root.defaults[:model] = :prototype
|
396
|
+
root.define.bar { Object.new }
|
397
|
+
f1 = root.foo
|
398
|
+
f2 = root.foo
|
399
|
+
assert_same f1, f2
|
400
|
+
b1 = root.bar
|
401
|
+
b2 = root.bar
|
402
|
+
assert_not_same b1, b2
|
403
|
+
end
|
404
|
+
|
405
|
+
def test_use_bang_without_block
|
406
|
+
root = new_container
|
407
|
+
original = root.use! :model => :prototype
|
408
|
+
assert original.empty?
|
409
|
+
o2 = root.use! :pipeline => [ :threaded ]
|
410
|
+
assert_nil root.defaults[:model]
|
411
|
+
assert_equal [ :threaded ], root.defaults[:pipeline]
|
412
|
+
root.use! o2
|
413
|
+
assert_nil root.defaults[:pipeline]
|
414
|
+
assert_equal :prototype, root.defaults[:model]
|
415
|
+
end
|
416
|
+
|
417
|
+
def test_use_bang_with_block
|
418
|
+
root = new_container
|
419
|
+
root.use! :model => :prototype do |r|
|
420
|
+
assert_same root, r
|
421
|
+
assert_equal :prototype, root.defaults[:model]
|
422
|
+
root.use! :pipeline => [ :threaded ] do
|
423
|
+
assert_nil root.defaults[:model]
|
424
|
+
assert_equal [ :threaded ], root.defaults[:pipeline]
|
425
|
+
end
|
426
|
+
assert_nil root.defaults[:pipeline]
|
427
|
+
assert_equal :prototype, root.defaults[:model]
|
428
|
+
end
|
429
|
+
assert_nil root.defaults[:model]
|
430
|
+
end
|
431
|
+
|
432
|
+
def test_use_without_block
|
433
|
+
root = new_container
|
434
|
+
o1 = root.use :model => :prototype
|
435
|
+
o2 = root.use :pipeline => [ :threaded ]
|
436
|
+
assert_equal :prototype, root.defaults[:model]
|
437
|
+
assert_equal [ :threaded ], root.defaults[:pipeline]
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_use_with_block
|
441
|
+
root = new_container
|
442
|
+
root.use :model => :prototype do |r|
|
443
|
+
assert_same root, r
|
444
|
+
assert_equal :prototype, root.defaults[:model]
|
445
|
+
root.use :pipeline => [ :threaded ] do
|
446
|
+
assert_equal :prototype, root.defaults[:model]
|
447
|
+
assert_equal [ :threaded ], root.defaults[:pipeline]
|
448
|
+
end
|
449
|
+
assert_nil root.defaults[:pipeline]
|
450
|
+
assert_equal :prototype, root.defaults[:model]
|
451
|
+
end
|
452
|
+
assert_nil root.defaults[:model]
|
453
|
+
end
|
454
|
+
|
373
455
|
end
|