needle 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,344 @@
1
+ <html>
2
+ <head>
3
+ <title>Needle Manual :: Chapter 9: Customizing Needle</title>
4
+ <link type="text/css" rel="stylesheet" href="manual.css" />
5
+ </head>
6
+
7
+ <body>
8
+ <div id="banner">
9
+ <table border='0' cellpadding='0' cellspacing='0' width='100%'>
10
+ <tr><td valign='top' align='left'>
11
+ <div class="title">
12
+ <span class="product">Needle&mdash;</span><br />
13
+ <span class="tagline">to the point --></span>
14
+ </div>
15
+ </td><td valign='middle' align='right'>
16
+ <div class="info">
17
+ Needle Version: <strong>1.1.0</strong><br />
18
+ Manual Last Updated: <strong>2004-11-11 17:31 GMT</strong>
19
+ </div>
20
+ </td></tr>
21
+ </table>
22
+ </div>
23
+
24
+ <table border='0' width='100%' cellpadding='0' cellspacing='0'>
25
+ <tr><td valign='top'>
26
+
27
+ <div id="navigation">
28
+ <h1>Needle Manual</h1>
29
+
30
+ <h2>Chapters</h2>
31
+ <ol type="I">
32
+
33
+ <li>
34
+ <a href="chapter-1.html">
35
+ Introduction
36
+ </a>
37
+
38
+ <ol type="1">
39
+
40
+ <li><a href="chapter-1.html#s1">What is Needle?</a></li>
41
+
42
+ <li><a href="chapter-1.html#s2">How Can It Help Me?</a></li>
43
+
44
+ <li><a href="chapter-1.html#s3">Alternatives</a></li>
45
+
46
+ <li><a href="chapter-1.html#s4">License Information</a></li>
47
+
48
+ <li><a href="chapter-1.html#s5">Support</a></li>
49
+
50
+ </ol>
51
+ </li>
52
+
53
+ <li>
54
+ <a href="chapter-2.html">
55
+ Registry
56
+ </a>
57
+
58
+ <ol type="1">
59
+
60
+ <li><a href="chapter-2.html#s1">Overview</a></li>
61
+
62
+ <li><a href="chapter-2.html#s2">Creating</a></li>
63
+
64
+ <li><a href="chapter-2.html#s3">Services</a></li>
65
+
66
+ <li><a href="chapter-2.html#s4">Namespaces</a></li>
67
+
68
+ </ol>
69
+ </li>
70
+
71
+ <li>
72
+ <a href="chapter-3.html">
73
+ Service Locator
74
+ </a>
75
+
76
+ <ol type="1">
77
+
78
+ <li><a href="chapter-3.html#s1">Overview</a></li>
79
+
80
+ <li><a href="chapter-3.html#s2">Conventional Architecture</a></li>
81
+
82
+ <li><a href="chapter-3.html#s3">Locator Pattern</a></li>
83
+
84
+ </ol>
85
+ </li>
86
+
87
+ <li>
88
+ <a href="chapter-4.html">
89
+ Dependency Injection
90
+ </a>
91
+
92
+ <ol type="1">
93
+
94
+ <li><a href="chapter-4.html#s1">Overview</a></li>
95
+
96
+ <li><a href="chapter-4.html#s2">Setup</a></li>
97
+
98
+ </ol>
99
+ </li>
100
+
101
+ <li>
102
+ <a href="chapter-5.html">
103
+ Interceptors
104
+ </a>
105
+
106
+ <ol type="1">
107
+
108
+ <li><a href="chapter-5.html#s1">Overview</a></li>
109
+
110
+ <li><a href="chapter-5.html#s2">Architecture</a></li>
111
+
112
+ <li><a href="chapter-5.html#s3">Attaching</a></li>
113
+
114
+ <li><a href="chapter-5.html#s4">Ordering</a></li>
115
+
116
+ <li><a href="chapter-5.html#s5">Custom</a></li>
117
+
118
+ </ol>
119
+ </li>
120
+
121
+ <li>
122
+ <a href="chapter-6.html">
123
+ Service Models
124
+ </a>
125
+
126
+ <ol type="1">
127
+
128
+ <li><a href="chapter-6.html#s1">Overview</a></li>
129
+
130
+ <li><a href="chapter-6.html#s2">Pipelines</a></li>
131
+
132
+ <li><a href="chapter-6.html#s3">Models</a></li>
133
+
134
+ </ol>
135
+ </li>
136
+
137
+ <li>
138
+ <a href="chapter-7.html">
139
+ Logging
140
+ </a>
141
+
142
+ <ol type="1">
143
+
144
+ <li><a href="chapter-7.html#s1">Overview</a></li>
145
+
146
+ <li><a href="chapter-7.html#s2">LogFactory</a></li>
147
+
148
+ <li><a href="chapter-7.html#s3">Configuration</a></li>
149
+
150
+ </ol>
151
+ </li>
152
+
153
+ <li>
154
+ <a href="chapter-8.html">
155
+ Service Libraries
156
+ </a>
157
+
158
+ <ol type="1">
159
+
160
+ <li><a href="chapter-8.html#s1">Overview</a></li>
161
+
162
+ <li><a href="chapter-8.html#s2">Creating Libraries</a></li>
163
+
164
+ <li><a href="chapter-8.html#s3">Using Libraries</a></li>
165
+
166
+ </ol>
167
+ </li>
168
+
169
+ <li><strong>
170
+ <a href="chapter-9.html">
171
+ Customizing Needle
172
+ </a>
173
+ </strong> <big>&larr;</big>
174
+ <ol type="1">
175
+
176
+ <li><a href="chapter-9.html#s1">Namespaces</a></li>
177
+
178
+ <li><a href="chapter-9.html#s2">Interceptors</a></li>
179
+
180
+ <li><a href="chapter-9.html#s3">Contexts</a></li>
181
+
182
+ </ol>
183
+ </li>
184
+
185
+ </ol>
186
+
187
+ <h2>Other Documentation</h2>
188
+
189
+ <ul>
190
+ <li><a href="http://needle.rubyforge.org/api/index.html">Needle API</a></li>
191
+ <li><a href="http://needle.rubyforge.org/faq.html">Needle FAQ</a></li>
192
+ </ul>
193
+
194
+ <h2>Tutorials</h2>
195
+ <ol>
196
+
197
+ </ol>
198
+
199
+ <p align="center"><strong>More To Come...</strong></p>
200
+
201
+ <div class="license">
202
+ <a href="http://creativecommons.org/licenses/by-sa/2.0/"><img alt="Creative Commons License" border="0" src="http://creativecommons.org/images/public/somerights" /></a><br />
203
+ This manual is licensed under a <a href="http://creativecommons.org/licenses/by-sa/2.0/">Creative Commons License</a>.
204
+ </div>
205
+ </div>
206
+
207
+ </td><td valign='top' width="100%">
208
+
209
+ <div id="content">
210
+
211
+ <h1>9. Customizing Needle</h1>
212
+
213
+
214
+
215
+ <h2>
216
+ <a name="s1"></a>
217
+ 9.1. Namespaces
218
+ </h2>
219
+
220
+
221
+
222
+ <div class="section">
223
+ <p>By default, when you create a namespace in Needle, the namespace is registered as a service. The type of the service is determined by the <code>:namespace_impl_factory</code> service, which (by default) returns the <code>Needle::Container</code> class.</p>
224
+
225
+ <p>You can specify your own custom implementation for namespaces by registering your own <code>:namespace_impl_factory</code> service. In fact, each namespace can have its own implementation of subnamespaces&#8212;just register a <code>:namespace_impl_factory</code> in each one that you want to be specialized.</p>
226
+
227
+ <p>Here&#8217;s a contrived example. Suppose you want each namespace to keep track of the precise time that it was created.</p>
228
+
229
+ <pre>
230
+ class TimeTrackerNamespace &lt; Needle::Container
231
+ attr_reader :birth_date
232
+
233
+ def initialize( *args )
234
+ super
235
+ @birth_date = Time.now
236
+ end
237
+ end
238
+
239
+ reg = Needle::Registry.new
240
+ reg.register( :namespace_impl_factory ) { TimeTrackerNamespace }
241
+
242
+ reg.namespace :hello
243
+ p reg.hello.birth_date
244
+ </pre>
245
+
246
+ <p>In general, you&#8217;ll be better off having your custom implementation extend <code>Needle::Container</code>, although the only <em>real</em> requirement is that your implementation publish the same interface as the default namespace implementation.<br />
247
+ </p>
248
+ </div>
249
+
250
+
251
+
252
+ <h2>
253
+ <a name="s2"></a>
254
+ 9.2. Interceptors
255
+ </h2>
256
+
257
+
258
+
259
+ <div class="section">
260
+ <p>When you attach an interceptor to a service, that new interceptor is wrapped in a definition object that includes various metadata about the interceptor, including its implementation, its priority, its name, and so forth. The implementation of this interceptor definition is determined by the value of the <code>:interceptor_impl_factory</code> service, which by default returns <code>Needle::Interceptor</code>.</p>
261
+
262
+ <p>It is this wrapper object that allows interceptor definitions to be done using method chaining:</p>
263
+
264
+ <pre>
265
+ reg.intercept( :foo ).with { ... }.with_options(...)
266
+ </pre>
267
+
268
+ <p>If you wish to add custom, domain-specific functionality to the interceptor wrapper, you can register your own implementation of the <code>:interceptor_impl_factory</code>. Consider the following contrived example, where an &#8220;only_if&#8221; clause is given to determine when the interceptor should be invoked.</p>
269
+
270
+ <pre>
271
+ class OnlyIfInterceptor &lt; Needle::Interceptor
272
+ def only_if( &#38;block )
273
+ @only_if = block
274
+ self
275
+ end
276
+
277
+ def action
278
+ action_proc = super
279
+ lambda do |chain,ctx|
280
+ if @only_if.call( chain, ctx )
281
+ action_proc.call( chain, ctx )
282
+ else
283
+ chain.process_next( ctx )
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ reg = Needle::Registry.new
290
+ reg.register( :interceptor_impl_factory ) { OnlyIfInterceptor }
291
+ reg.register( :foo ) { Bar.new }
292
+
293
+ reg.intercept( :foo ).
294
+ with { |c| c.logging_interceptor }.
295
+ only_if { |ch,ctx| something_is_true( ch, ctx ) }.
296
+ with_options(...)
297
+ </pre>
298
+ </div>
299
+
300
+
301
+
302
+ <h2>
303
+ <a name="s3"></a>
304
+ 9.3. Contexts
305
+ </h2>
306
+
307
+
308
+
309
+ <div class="section">
310
+ <p>A <em>definition context</em> is used when registering services using any of the <code>#define</code> interfaces. For example, <code>Container#define</code> yields an instance of a definition context to the given block, and <code>Container#define!</code> uses the block in an <code>instance_eval</code> on a definition context.</p>
311
+
312
+ <p>The default implementation used for definition contexts is defined by the <code>:definition_context_factory</code> service. By default, this service returns <code>Needle::DefinitionContext</code>, but you can specify your own definition context implementations by overriding this service. In fact, each namespace could have its own definition context implementation, if needed.</p>
313
+
314
+ <p>Consider the following contrived example, where you want to provide a convenient way to register services of type Hash.</p>
315
+
316
+ <pre>
317
+ class MyDefinitionContext &lt; Needle::DefinitionContext
318
+ def register_hash( name, opts={} )
319
+ this_container.register( name, opts ) { Hash.new }
320
+ end
321
+ end
322
+
323
+ reg = Needle::Registry.new
324
+ reg.register( :definition_context_factory ) { MyDefinitionContext }
325
+
326
+ reg.define do |b|
327
+ b.register_hash( :test1 )
328
+ b.register_hash( :test2 )
329
+ end
330
+
331
+ reg.test1[:key] = "value"
332
+ reg.test2[:foo] = "bar"
333
+ </pre>
334
+ </div>
335
+
336
+
337
+
338
+
339
+ </div>
340
+
341
+ </td></tr>
342
+ </table>
343
+ </body>
344
+ </html>
@@ -14,8 +14,8 @@
14
14
  </div>
15
15
  </td><td valign='middle' align='right'>
16
16
  <div class="info">
17
- Needle Version: <strong>1.0.0</strong><br />
18
- Manual Last Updated: <strong>2004-11-04 14:24 GMT</strong>
17
+ Needle Version: <strong>1.1.0</strong><br />
18
+ Manual Last Updated: <strong>2004-11-11 17:31 GMT</strong>
19
19
  </div>
20
20
  </td></tr>
21
21
  </table>
@@ -166,6 +166,22 @@
166
166
  </ol>
167
167
  </li>
168
168
 
169
+ <li>
170
+ <a href="chapter-9.html">
171
+ Customizing Needle
172
+ </a>
173
+
174
+ <ol type="1">
175
+
176
+ <li><a href="chapter-9.html#s1">Namespaces</a></li>
177
+
178
+ <li><a href="chapter-9.html#s2">Interceptors</a></li>
179
+
180
+ <li><a href="chapter-9.html#s3">Contexts</a></li>
181
+
182
+ </ol>
183
+ </li>
184
+
169
185
  </ol>
170
186
 
171
187
  <h2>Other Documentation</h2>
@@ -218,7 +234,7 @@
218
234
  <table border='0' cellpadding='0' cellspacing='0' align='center'><tr><td>
219
235
  <ul>
220
236
 
221
- <li>added remaining chapters</li>
237
+ <li>Added chapter on "Customizing Needle"</li>
222
238
 
223
239
  </ul>
224
240
  </table>
@@ -15,7 +15,6 @@
15
15
  #++
16
16
 
17
17
  require 'needle/errors'
18
- require 'needle/interceptor'
19
18
  require 'needle/service-point'
20
19
 
21
20
  module Needle
@@ -30,83 +29,6 @@ module Needle
30
29
  # Container#namespace method to create new containers.
31
30
  class Container
32
31
 
33
- # This class is used by the #define! and #namespace! methods to allow
34
- # an +instance_eval+'d block to create new service points simply by
35
- # invoking imaginary methods. It is basically an empty shell, with almost
36
- # all of the builtin methods removed from it. (This allows services like
37
- # "hash" and "print" to be defined, where they would normally conflict
38
- # with the Kernel methods of the same name.)
39
- class DefinitionContext
40
- ( private_instance_methods +
41
- protected_instance_methods +
42
- public_instance_methods -
43
- [ "instance_eval", "__id__", "__send__", "initialize", "remove_const",
44
- "method_missing", "method", "class", "inspect", "to_s", "instance_variables" ]
45
- ).
46
- each { |m| undef_method m }
47
-
48
- # Create a new DefinitionContext that wraps the given container. All
49
- # operations performed on this context will be delegated to the
50
- # container.
51
- def initialize( container )
52
- @container = container
53
- end
54
-
55
- # A way to access the container reference being operated on from within
56
- # the context.
57
- def this_container
58
- @container
59
- end
60
-
61
- # Delegate to Container#intercept.
62
- def intercept( name )
63
- @container.intercept( name )
64
- end
65
-
66
- # Delegate to Container#namespace.
67
- def namespace( *parms, &block )
68
- @container.namespace( *parms, &block )
69
- end
70
-
71
- # Delegate to Container#namespace_define!.
72
- def namespace_define!( *parms, &block )
73
- @container.namespace_define!( *parms, &block )
74
- end
75
-
76
- alias :namespace! :namespace_define!
77
-
78
- # Delegate to Container#define on the new namespace.
79
- def namespace_define( *parms, &block )
80
- @container.namespace( *parms ) { |ns| ns.define( &block ) }
81
- end
82
-
83
- # Delegate to Container#require on the current container.
84
- def require( *parms )
85
- # this is necessary to work around an rdoc bug...rdoc doesn't like
86
- # calling require with a variable number of arguments.
87
- @container.__send__( :require, *parms )
88
- end
89
-
90
- # Any method invocation with no block and no parameters is interpreted to
91
- # be a service reference on the wrapped container, and delegates to
92
- # Container#[]. If the block is not given but the args are not empty, a
93
- # NoMethodError will be raised.
94
- #
95
- # If a block is given, this delegates to Container#register, leaving
96
- # all parameters in place.
97
- def method_missing( sym, *args, &block )
98
- if block.nil?
99
- if args.empty?
100
- @container[sym]
101
- else
102
- super
103
- end
104
- else
105
- @container.register( sym, *args, &block )
106
- end
107
- end
108
- end
109
-
110
32
  # The container that contains this container. This will be +nil+ for
111
33
  # the root of a hierarchy (see Registry).
112
34
  attr_reader :parent
@@ -145,7 +67,7 @@ module Needle
145
67
  # Returns the DefinitionContext instance that can be used to "build"
146
68
  # this container.
147
69
  def builder
148
- @builder ||= DefinitionContext.new( self )
70
+ @builder ||= self[ :definition_context_factory ].new( self )
149
71
  end
150
72
 
151
73
  # If a block is given, yields the container's builder instance to the
@@ -208,7 +130,7 @@ module Needle
208
130
  # with the new namespace passed to it.
209
131
  #
210
132
  # For the curious, namespaces are simply services that are implemented
211
- # by Container. The two statements are really identical:
133
+ # by Container. The two statements are conceptually identical:
212
134
  #
213
135
  # container.namespace( :calc )
214
136
  # container.register( :calc ) { |c,p| Needle::Container.new( c, p.name ) }
@@ -233,7 +155,7 @@ module Needle
233
155
  # namespace as soon as you've created it.
234
156
  def namespace( name, opts={}, &block )
235
157
  register( name, opts ) do |c,p|
236
- ns = Container.new( c, name )
158
+ ns = self[ :namespace_impl_factory ].new( c, name )
237
159
  block.call ns if block
238
160
  ns
239
161
  end
@@ -322,7 +244,7 @@ module Needle
322
244
  point = find_definition( name )
323
245
  raise ServiceNotFound, "#{fullname}.#{name}" unless point
324
246
 
325
- interceptor = Interceptor.new
247
+ interceptor = self[ :interceptor_impl_factory ].new
326
248
  point.interceptor interceptor
327
249
 
328
250
  interceptor
@@ -0,0 +1,100 @@
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/errors'
18
+ require 'needle/service-point'
19
+
20
+ module Needle
21
+
22
+ # This class is used by the Container#define! and Container#namespace!
23
+ # methods to allow an +instance_eval+'d block to create new service points
24
+ # simply by invoking imaginary methods. It is basically an empty shell, with
25
+ # almost all of the builtin methods removed from it. (This allows services
26
+ # like "hash" and "print" to be defined, where they would normally conflict
27
+ # with the Kernel methods of the same name.)
28
+ class DefinitionContext
29
+ ( private_instance_methods +
30
+ protected_instance_methods +
31
+ public_instance_methods -
32
+ [ "instance_eval", "__id__", "__send__", "initialize", "remove_const",
33
+ "method_missing", "method", "class", "inspect", "to_s", "instance_variables" ]
34
+ ).
35
+ each { |m| undef_method m }
36
+
37
+ # Create a new DefinitionContext that wraps the given container. All
38
+ # operations performed on this context will be delegated to the
39
+ # container.
40
+ def initialize( container )
41
+ @container = container
42
+ end
43
+
44
+ # A way to access the container reference being operated on from within
45
+ # the context.
46
+ def this_container
47
+ @container
48
+ end
49
+
50
+ # Delegate to Container#intercept.
51
+ def intercept( name )
52
+ @container.intercept( name )
53
+ end
54
+
55
+ # Delegate to Container#namespace.
56
+ def namespace( *parms, &block )
57
+ @container.namespace( *parms, &block )
58
+ end
59
+
60
+ # Delegate to Container#namespace_define!.
61
+ def namespace_define!( *parms, &block )
62
+ @container.namespace_define!( *parms, &block )
63
+ end
64
+
65
+ alias :namespace! :namespace_define!
66
+
67
+ # Delegate to Container#define on the new namespace.
68
+ def namespace_define( *parms, &block )
69
+ @container.namespace( *parms ) { |ns| ns.define( &block ) }
70
+ end
71
+
72
+ # Delegate to Container#require on the current container.
73
+ def require( *parms )
74
+ # this is necessary to work around an rdoc bug...rdoc doesn't like
75
+ # calling require with a variable number of arguments.
76
+ @container.__send__( :require, *parms )
77
+ end
78
+
79
+ # Any method invocation with no block and no parameters is interpreted to
80
+ # be a service reference on the wrapped container, and delegates to
81
+ # Container#[]. If the block is not given but the args are not empty, a
82
+ # NoMethodError will be raised.
83
+ #
84
+ # If a block is given, this delegates to Container#register, leaving
85
+ # all parameters in place.
86
+ def method_missing( sym, *args, &block )
87
+ if block.nil?
88
+ if args.empty?
89
+ @container[sym]
90
+ else
91
+ super
92
+ end
93
+ else
94
+ @container.register( sym, *args, &block )
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -15,7 +15,9 @@
15
15
  #++
16
16
 
17
17
  require 'needle/container'
18
+ require 'needle/definition-context'
18
19
  require 'needle/errors'
20
+ require 'needle/interceptor'
19
21
  require 'needle/lifecycle/deferred'
20
22
  require 'needle/lifecycle/initialize'
21
23
  require 'needle/lifecycle/singleton'
@@ -73,9 +75,13 @@ module Needle
73
75
  new( *parms ) { |reg| reg.define( &block ) }
74
76
  end
75
77
 
76
- # Instantiate a new Registry. The options hash may include options
77
- # used to initialize the logger factory. The logger factory options
78
- # should be in another hash, keyed by the value <tt>:logs</tt>.
78
+ # Instantiate a new Registry. The options hash may include the following
79
+ # keys:
80
+ #
81
+ # <tt>:logs</tt>:: options used to initialize the logger factory. The
82
+ # value to this key should be another hash.
83
+ # <tt>:parent</tt>:: The parent container of this registry.
84
+ # <tt>:name</tt>:: The name of this registry.
79
85
  #
80
86
  # If a block is given, the constructed registry instance is yielded to it.
81
87
  #
@@ -95,19 +101,20 @@ module Needle
95
101
  # :logs => { :filename => "/dev/null" }
96
102
  # )
97
103
  def initialize( opts={} )
98
- super( nil, nil )
99
- bootstrap( opts )
104
+ super( opts[:parent], opts[:name] )
105
+ bootstrap( opts ) if parent.nil?
100
106
  yield( self ) if block_given?
101
107
  end
102
108
 
103
- # Returns +nil+. Registries are unnamed containers.
109
+ # Returns +nil+, unless the registry has a parent, in which case it acts
110
+ # like Container#fullname. Registries are usually unnamed containers.
104
111
  def fullname
105
- nil
112
+ parent ? super : nil
106
113
  end
107
114
 
108
115
  # Bootstraps the pipeline elements, service models, logger factory, and
109
116
  # logging interceptor services into the current registry. This is only
110
- # called when a new registry is created.
117
+ # called when a new, root (parentless) registry is created.
111
118
  def bootstrap( opts )
112
119
  register( :pipeline_elements, :pipeline=>[] ) { Hash.new }
113
120
  pipeline( :pipeline_elements ).add( :singleton,
@@ -137,6 +144,10 @@ module Needle
137
144
  :threaded_deferred_initialize => [ :threaded, :deferred, :initialize ]
138
145
  )
139
146
 
147
+ register( :definition_context_factory ) { DefinitionContext }
148
+ register( :namespace_impl_factory ) { Container }
149
+ register( :interceptor_impl_factory ) { Interceptor }
150
+
140
151
  register( :logs ) { LogFactory.new( opts[:logs] || {} ) }
141
152
  register( :logging_interceptor ) { LoggingInterceptor }
142
153
  end
@@ -18,7 +18,7 @@ module Needle
18
18
  module Version
19
19
 
20
20
  MAJOR = 1
21
- MINOR = 0
21
+ MINOR = 1
22
22
  TINY = 0
23
23
 
24
24
  # The version of the Needle library in use.