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.
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