Module: PluginFactory

Included in:
Mongrel::Command::Command
Defined in:
lib/pluginfactory.rb

Overview

A mixin that adds PluginFactory class methods to a base class, so that subclasses may be instantiated by name.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.logger_callbackObject

Returns the value of attribute logger_callback.



107
108
109
# File 'lib/pluginfactory.rb', line 107

def logger_callback
  @logger_callback
end

Class Method Details

.extend_object(obj) ⇒ Object

Raise an exception if the object being extended is anything but a class.



124
125
126
127
128
129
130
# File 'lib/pluginfactory.rb', line 124

def self::extend_object( obj )
	unless obj.is_a?( Class )
		raise TypeError, "Cannot extend a #{obj.class.name}", caller(1)
	end
	obj.instance_variable_set( :@derivatives, {} )
	super
end

.included(klass) ⇒ Object

Inclusion callback – extends the including class.



117
118
119
# File 'lib/pluginfactory.rb', line 117

def self::included( klass )
	klass.extend( self )
end

.log(level, *msg) ⇒ Object

If the logger callback is set, use it to pass on a log entry. First argument is



111
112
113
# File 'lib/pluginfactory.rb', line 111

def self::log(level, *msg)
	@logger_callback.call(level, msg.join) if @logger_callback
end

Instance Method Details

#create(subType, *args, &block) ⇒ Object

Given the className of the class to instantiate, and other arguments bound for the constructor of the new object, this method loads the derivative class if it is not loaded already (raising a LoadError if an appropriately-named file cannot be found), and instantiates it with the given args. The className may be the the fully qualified name of the class, the class object itself, or the unique part of the class name. The following examples would all try to load and instantiate a class called “FooListener” if Listener included Factory

obj = Listener::create( 'FooListener' )
obj = Listener::create( FooListener )
obj = Listener::create( 'Foo' )


208
209
210
211
212
213
214
215
216
# File 'lib/pluginfactory.rb', line 208

def create( subType, *args, &block )
	subclass = getSubclass( subType )

	return subclass.new( *args, &block )
rescue => err
	nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
	msg = "When creating '#{subType}': " + err.message
	Kernel::raise( err.class, msg, nicetrace )
end

#derivativeClassesObject

Returns an Array of registered derivatives



191
192
193
# File 'lib/pluginfactory.rb', line 191

def derivativeClasses
	self.derivatives.values.uniq
end

#derivativesObject

Return the Hash of derivative classes, keyed by various versions of the class name.



139
140
141
142
143
144
145
# File 'lib/pluginfactory.rb', line 139

def derivatives
	ancestors.each {|klass|
		if klass.instance_variables.include?( "@derivatives" )
			break klass.instance_variable_get( :@derivatives )
		end
	}
end

#factoryTypeObject

Returns the type name used when searching for a derivative.

Raises:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/pluginfactory.rb', line 149

def factoryType
	base = nil
	self.ancestors.each {|klass|
		if klass.instance_variables.include?( "@derivatives" )
			base = klass
			break
		end
	}

	raise FactoryError, "Couldn't find factory base for #{self.name}" if
		base.nil?

	if base.name =~ /^.*::(.*)/
		return $1
	else
		return base.name
	end
end

#getModuleName(className) ⇒ Object

Build and return the unique part of the given className either by stripping leading namespaces if the name already has the name of the factory type in it (eg., ‘My::FooService’ for Service, or by appending the factory type if it doesn’t.



288
289
290
291
292
293
294
295
296
# File 'lib/pluginfactory.rb', line 288

def getModuleName( className )
	if className =~ /\w+#{self.factoryType}/
		modName = className.sub( /(?:.*::)?(\w+)(?:#{self.factoryType})/, "\\1" )
	else
		modName = className
	end

	return modName
end

#getSubclass(className) ⇒ Object

Given a className like that of the first argument to #create, attempt to load the corresponding class if it is not already loaded and return the class object.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/pluginfactory.rb', line 222

def getSubclass( className )
	return self if ( self.name == className || className == '' )
	return className if className.is_a?( Class ) && className >= self

	unless self.derivatives.has_key?( className.downcase )
		self.loadDerivative( className )

		unless self.derivatives.has_key?( className.downcase )
			raise FactoryError,
				"loadDerivative(%s) didn't add a '%s' key to the "\
				"registry for %s" %
				[ className, className.downcase, self.name ]
		end

		subclass = self.derivatives[ className.downcase ]
		unless subclass.is_a?( Class )
			raise FactoryError,
				"loadDerivative(%s) added something other than a class "\
				"to the registry for %s: %p" %
				[ className, self.name, subclass ]
		end
	end

	return self.derivatives[ className.downcase ]
end

#inherited(subclass) ⇒ Object

Inheritance callback – Register subclasses in the derivatives hash so that ::create knows about them.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/pluginfactory.rb', line 171

def inherited( subclass )
	keys = [ subclass.name, subclass.name.downcase, subclass ]

	# Handle class names like 'FooBar' for 'Bar' factories.
	if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factoryType})/i )
		keys << Regexp.last_match[1].downcase
	else
		keys << subclass.name.sub( /.*::/, '' ).downcase
	end

	keys.uniq.each {|key|
		#PluginFactory::log :info, "Registering %s derivative of %s as %p" %
		#	[ subclass.name, self.name, key ]
		self.derivatives[ key ] = subclass
	}
	super
end

#loadDerivative(className) ⇒ Object

Calculates an appropriate filename for the derived class using the name of the base class and tries to load it via require. If the including class responds to a method named derivativeDirs, its return value (either a String, or an array of Strings) is added to the list of prefix directories to try when attempting to require a modules. Eg., if class.derivativeDirs returns ['foo','bar'] the require line is tried with both 'foo/' and 'bar/' prepended to it.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/pluginfactory.rb', line 258

def loadDerivative( className )
	className = className.to_s

	#PluginFactory::log :debug, "Loading derivative #{className}"

	# Get the unique part of the derived class name and try to
	# load it from one of the derivative subdirs, if there are
	# any.
	modName = self.getModuleName( className )
	self.requireDerivative( modName )

	# Check to see if the specified listener is now loaded. If it
	# is not, raise an error to that effect.
	unless self.derivatives[ className.downcase ]
		raise FactoryError,
			"Couldn't find a %s named '%s'. Loaded derivatives are: %p" % [
			self.factoryType,
			className.downcase,
			self.derivatives.keys,
		], caller(3)
	end

	return true
end

#makeRequirePath(modname, subdir) ⇒ Object

Make a list of permutations of the given modname for the given subdir. Called on a DataDriver class with the arguments ‘Socket’ and ‘drivers’, returns:

["drivers/socketdatadriver", "drivers/socketDataDriver",
 "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/pluginfactory.rb', line 364

def makeRequirePath( modname, subdir )
	path = []
	myname = self.factoryType

	# Make permutations of the two parts
	path << modname
	path << modname.downcase
	path << modname			 + myname
	path << modname.downcase + myname
	path << modname.downcase + myname.downcase

	# If a non-empty subdir was given, prepend it to all the items in the
	# path
	unless subdir.nil? or subdir.empty?
		path.collect! {|m| File::join(subdir, m)}
	end

	return path.uniq.reverse
end

#requireDerivative(modName) ⇒ Object

If the factory responds to the #derivativeDirs method, call it and use the returned array as a list of directories to search for the module with the specified modName.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/pluginfactory.rb', line 302

def requireDerivative( modName )

	# See if we have a list of special subdirs that derivatives
	# live in
	if ( self.respond_to?(:derivativeDirs) )
		subdirs = self.derivativeDirs
		subdirs = [ subdirs ] unless subdirs.is_a?( Array )

	# If not, just try requiring it from $LOAD_PATH
	else
		subdirs = ['']
	end

	fatals = []

	# Iterate over the subdirs until we successfully require a
	# module.
	catch( :found ) {
		subdirs.collect {|dir| dir.strip}.each do |subdir|
			self.makeRequirePath( modName, subdir ).each {|path|
				#PluginFactory::log :debug, "Trying #{path}..."

				# Try to require the module, saving errors and jumping
				# out of the catch block on success.
				begin
					require( path.untaint )
				rescue LoadError => err
					PluginFactory::log :debug,
						"No module at '%s', trying the next alternative: '%s'" %
						[ path, err.message ]
				rescue ScriptError,StandardError => err
					fatals << err
					PluginFactory::log :error,
						"Found '#{path}', but encountered an error: %s\n\t%s" %
						[ err.message, err.backtrace.join("\n\t") ]
				else
					#PluginFactory::log :debug, 
					#	"Found '#{path}'. Throwing :found"
					throw :found
				end
			}
		end

		#PluginFactory::log :debug, "fatals = %p" % [ fatals ]

		# Re-raise is there was a file found, but it didn't load for
		# some reason.
		if ! fatals.empty?
			#PluginFactory::log :debug, "Re-raising first fatal error"
			Kernel::raise( fatals.first )
		end

		nil
	}
end