needle 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/benchmarks/instantiability.rb +26 -0
  2. data/benchmarks/instantiation.rb +33 -0
  3. data/benchmarks/interceptors.rb +42 -0
  4. data/benchmarks/interceptors2.rb +70 -0
  5. data/doc/README +3 -1
  6. data/doc/di-in-ruby.rdoc +201 -0
  7. data/doc/images/di_classdiagram.jpg +0 -0
  8. data/doc/manual/manual.yml +4 -0
  9. data/doc/manual/parts/01_alternatives.txt +11 -0
  10. data/doc/manual/parts/02_creating.txt +20 -0
  11. data/doc/manual/parts/02_namespaces.txt +1 -1
  12. data/doc/manual/parts/02_services.txt +15 -3
  13. data/doc/manual-html/chapter-1.html +34 -7
  14. data/doc/manual-html/chapter-2.html +43 -9
  15. data/doc/manual-html/chapter-3.html +6 -4
  16. data/doc/manual-html/chapter-4.html +6 -4
  17. data/doc/manual-html/chapter-5.html +6 -4
  18. data/doc/manual-html/chapter-6.html +6 -4
  19. data/doc/manual-html/chapter-7.html +6 -4
  20. data/doc/manual-html/index.html +19 -4
  21. data/lib/needle/container.rb +104 -66
  22. data/{test/tc_models.rb → lib/needle/lifecycle/deferred.rb} +14 -20
  23. data/lib/needle/lifecycle/initialize.rb +49 -0
  24. data/lib/needle/{models → lifecycle}/proxy.rb +16 -8
  25. data/lib/needle/lifecycle/singleton.rb +63 -0
  26. data/lib/needle/lifecycle/threaded.rb +58 -0
  27. data/lib/needle/pipeline/collection.rb +133 -0
  28. data/lib/needle/pipeline/element.rb +85 -0
  29. data/lib/needle/pipeline/interceptor.rb +46 -0
  30. data/lib/needle/registry.rb +48 -8
  31. data/lib/needle/service-point.rb +36 -39
  32. data/lib/needle/thread.rb +87 -0
  33. data/lib/needle/version.rb +1 -1
  34. data/{lib/needle/models/prototype.rb → test/lifecycle/tc_deferred.rb} +15 -17
  35. data/test/lifecycle/tc_initialize.rb +62 -0
  36. data/test/{models → lifecycle}/tc_proxy.rb +5 -5
  37. data/test/lifecycle/tc_singleton.rb +32 -0
  38. data/{lib/needle/models/prototype-deferred.rb → test/lifecycle/tc_threaded.rb} +24 -18
  39. data/test/models/model_test.rb +131 -0
  40. data/test/models/tc_prototype.rb +9 -30
  41. data/test/models/tc_prototype_deferred.rb +9 -31
  42. data/test/models/tc_prototype_deferred_initialize.rb +32 -0
  43. data/test/models/tc_prototype_initialize.rb +32 -0
  44. data/test/models/tc_singleton.rb +8 -29
  45. data/test/models/tc_singleton_deferred.rb +8 -30
  46. data/test/models/tc_singleton_deferred_initialize.rb +32 -0
  47. data/test/models/tc_singleton_initialize.rb +32 -0
  48. data/test/models/tc_threaded.rb +32 -0
  49. data/test/models/tc_threaded_deferred.rb +32 -0
  50. data/test/models/tc_threaded_deferred_initialize.rb +32 -0
  51. data/test/models/tc_threaded_initialize.rb +32 -0
  52. data/test/pipeline/tc_collection.rb +116 -0
  53. data/test/pipeline/tc_element.rb +72 -0
  54. data/test/tc_container.rb +77 -36
  55. data/test/tc_logger.rb +5 -0
  56. data/test/tc_registry.rb +39 -1
  57. data/test/tc_service_point.rb +43 -7
  58. metadata +39 -12
  59. data/lib/needle/models/singleton-deferred.rb +0 -57
  60. data/lib/needle/models/singleton.rb +0 -56
  61. 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 Models
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 given service point.
27
- def initialize( container, &callback )
28
- @container = container
29
- @callback = callback
30
- @mutex = Mutex.new
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( @container )
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
@@ -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/models'
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 #register!
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.register!( &block ) }
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
- # (This includes options used to initialize the logger factory.
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 logging interceptor
99
- # services into the current registry. This is only called when a new
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
- Models.register( self )
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
@@ -14,7 +14,8 @@
14
14
  # =============================================================================
15
15
  #++
16
16
 
17
- require 'needle/interceptor-chain'
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 Container#register and Container#register! interfaces.
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 should either be a class (which implements
43
- # the necessary interface to be a service model), a symbol (in which case
44
- # it refers to a service model that has been registered in the root
45
- # <tt>:service_models</tt> service), or a string (in which case it is
46
- # internalized and converted to a symbol). All other values are converted
47
- # to strings and then internalized.
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
- @interceptors = nil
53
-
54
- model = opts[:model] || :singleton
55
-
56
- case model
57
- when Class then
58
- model_factory = model
59
- when Symbol then
60
- model_factory = @container.root.service_models[model]
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
- @model = model_factory.new( @container, opts ) { instantiate }
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
- @interceptors ||= []
83
- @interceptors.push interceptor
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
- @model.instance
91
- end
92
-
93
- # Do the dirty-work of instantiating this service point. This invokes the
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
- instance
101
+ @chain.call( @container, self )
104
102
  end
105
- private :instantiate
106
103
 
107
104
  end
108
105