1POE::Component::PluggabUlsee(r3)Contributed Perl DocumenPtOaEt:i:oCnomponent::Pluggable(3)
2
3
4
6 POE::Component::Pluggable - A base class for creating plugin-enabled
7 POE Components.
8
10 version 1.28
11
13 # A simple POE Component that sends ping events to registered sessions
14 # and plugins every second.
15
16 {
17 package SimplePoCo;
18
19 use strict;
20 use warnings;
21 use base qw(POE::Component::Pluggable);
22 use POE;
23 use POE::Component::Pluggable::Constants qw(:ALL);
24
25 sub spawn {
26 my ($package, %opts) = @_;
27 my $self = bless \%opts, $package;
28
29 $self->_pluggable_init(
30 prefix => 'simplepoco_',
31 types => [qw(EXAMPLE)],
32 debug => 1,
33 );
34
35 POE::Session->create(
36 object_states => [
37 $self => { shutdown => '_shutdown' },
38 $self => [qw(_send_ping _start register unregister __send_event)],
39 ],
40 );
41
42 return $self;
43 }
44
45 sub shutdown {
46 my ($self) = @_;
47 $poe_kernel->post($self->{session_id}, 'shutdown');
48 }
49
50 sub _pluggable_event {
51 my ($self) = @_;
52 $poe_kernel->post($self->{session_id}, '__send_event', @_);
53 }
54
55 sub _start {
56 my ($kernel, $self) = @_[KERNEL, OBJECT];
57 $self->{session_id} = $_[SESSION]->ID();
58
59 if ($self->{alias}) {
60 $kernel->alias_set($self->{alias});
61 }
62 else {
63 $kernel->refcount_increment($self->{session_id}, __PACKAGE__);
64 }
65
66 $kernel->delay(_send_ping => $self->{time} || 300);
67 return;
68 }
69
70 sub _shutdown {
71 my ($kernel, $self) = @_[KERNEL, OBJECT];
72
73 $self->_pluggable_destroy();
74 $kernel->alarm_remove_all();
75 $kernel->alias_remove($_) for $kernel->alias_list();
76 $kernel->refcount_decrement($self->{session_id}, __PACKAGE__) if !$self->{alias};
77 $kernel->refcount_decrement($_, __PACKAGE__) for keys %{ $self->{sessions} };
78
79 return;
80 }
81
82 sub register {
83 my ($kernel, $sender, $self) = @_[KERNEL, SENDER, OBJECT];
84 my $sender_id = $sender->ID();
85 $self->{sessions}->{$sender_id}++;
86
87 if ($self->{sessions}->{$sender_id} == 1) {
88 $kernel->refcount_increment($sender_id, __PACKAGE__);
89 $kernel->yield(__send_event => 'simplepoco_registered', $sender_id);
90 }
91
92 return;
93 }
94
95 sub unregister {
96 my ($kernel, $sender, $self) = @_[KERNEL, SENDER, OBJECT];
97 my $sender_id = $sender->ID();
98 my $record = delete $self->{sessions}->{$sender_id};
99
100 if ($record) {
101 $kernel->refcount_decrement($sender_id, __PACKAGE__);
102 $kernel->yield(__send_event => 'simplepoco_unregistered', $sender_id);
103 }
104
105 return;
106 }
107
108 sub __send_event {
109 my ($kernel, $self, $event, @args) = @_[KERNEL, OBJECT, ARG0..$#_];
110
111 return 1 if $self->_pluggable_process(EXAMPLE => $event, \(@args)) == PLUGIN_EAT_ALL;
112 $kernel->post($_, $event, @args) for keys %{ $self->{sessions} };
113 }
114
115 sub _send_ping {
116 my ($kernel, $self) = @_[KERNEL, OBJECT];
117
118 $kernel->yield(__send_event => 'simplepoco_ping', 'Wake up sleepy');
119 $kernel->delay(_send_ping => $self->{time} || 1);
120 return;
121 }
122 }
123
124 {
125 package SimplePoCo::Plugin;
126 use strict;
127 use warnings;
128 use POE::Component::Pluggable::Constants qw(:ALL);
129
130 sub new {
131 my $package = shift;
132 return bless { @_ }, $package;
133 }
134
135 sub plugin_register {
136 my ($self, $pluggable) = splice @_, 0, 2;
137 print "Plugin added\n";
138 $pluggable->plugin_register($self, 'EXAMPLE', 'all');
139 return 1;
140 }
141
142 sub plugin_unregister {
143 print "Plugin removed\n";
144 return 1;
145 }
146
147 sub EXAMPLE_ping {
148 my ($self, $pluggable) = splice @_, 0, 2;
149 my $text = ${ $_[0] };
150 print "Plugin got '$text'\n";
151 return PLUGIN_EAT_NONE;
152 }
153 }
154
155 use strict;
156 use warnings;
157 use POE;
158
159 my $pluggable = SimplePoCo->spawn(
160 alias => 'pluggable',
161 time => 1,
162 );
163
164 POE::Session->create(
165 package_states => [
166 main => [qw(_start simplepoco_registered simplepoco_ping)],
167 ],
168 );
169
170 $poe_kernel->run();
171
172 sub _start {
173 my $kernel = $_[KERNEL];
174 $kernel->post(pluggable => 'register');
175 return;
176 }
177
178 sub simplepoco_registered {
179 print "Main program registered for events\n";
180 my $plugin = SimplePoCo::Plugin->new();
181 $pluggable->plugin_add('TestPlugin', $plugin);
182 return;
183 }
184
185 sub simplepoco_ping {
186 my ($heap, $text) = @_[HEAP, ARG0];
187 print "Main program got '$text'\n";
188 $heap->{got_ping}++;
189 $pluggable->shutdown() if $heap->{got_ping} == 3;
190 return;
191 }
192
194 POE::Component::Pluggable is a base class for creating plugin enabled
195 POE Components. It is a generic port of POE::Component::IRC's plugin
196 system.
197
198 If your component dispatches events to registered POE sessions, then
199 POE::Component::Pluggable may be a good fit for you.
200
201 Basic use would involve subclassing POE::Component::Pluggable, then
202 overriding _pluggable_event() and inserting _pluggable_process()
203 wherever you dispatch events from.
204
205 Users of your component can then load plugins using the plugin methods
206 provided to handle events generated by the component.
207
208 You may also use plugin style handlers within your component as
209 _pluggable_process() will attempt to process any events with local
210 method calls first. The return value of these handlers has the same
211 significance as the return value of 'normal' plugin handlers.
212
214 Subclassing POE::Component::Pluggable gives your object the following
215 'private' methods:
216
217 "_pluggable_init"
218 This should be called on your object after initialisation, but before
219 you want to start processing plugins. It accepts a number of
220 argument/value pairs:
221
222 'types', an arrayref of the types of events that your poco will support,
223 OR a hashref with the event types as keys and their abbrevations
224 (used as plugin event method prefixes) as values. This argument is
225 mandatory.
226
227 'prefix', the prefix for your events (default: 'pluggable_');
228 'reg_prefix', the prefix for the register()/unregister() plugin methods
229 (default: 'plugin_');
230 'debug', a boolean, if true, will cause a warning to be printed every time a
231 plugin call fails.
232
233 Notes: 'prefix' should probably end with a '_'. The types specify the
234 prefixes for plugin handlers. You can specify as many different types
235 as you require.
236
237 "_pluggable_destroy"
238 This should be called from any shutdown handler that your poco has. The
239 method unloads any loaded plugins.
240
241 "_pluggable_process"
242 This should be called before events are dispatched to interested
243 sessions. This gives pluggable a chance to discard events if requested
244 to by a plugin.
245
246 The first argument is a type, as specified to _pluggable_init().
247
248 sub _dispatch {
249 # stuff
250
251 return 1 if $self->_pluggable_process($type, $event, \(@args)) == PLUGIN_EAT_ALL;
252
253 # dispatch event to interested sessions.
254 }
255
256 This example demonstrates event arguments being passed as scalar refs
257 to the plugin system. This enables plugins to mangle the arguments if
258 necessary.
259
260 "_pluggable_event"
261 This method should be overridden in your class so that pipeline can
262 dispatch events through your event dispatcher. Pipeline sends a
263 prefixed 'plugin_add' and 'plugin_del' event whenever plugins are added
264 or removed, respectively. A prefixed 'plugin_error' event will be sent
265 if a plugin a) raises an exception, b) fails to return a true value
266 from its register/unregister methods, or c) fails to return a valid EAT
267 constant from a handler.
268
269 sub _pluggable_event {
270 my $self = shift;
271 $poe_kernel->post($self->{session_id}, '__send_event', @_);
272 }
273
274 There is an example of this in the SYNOPSIS.
275
277 Subclassing POE::Component::Pluggable gives your object the following
278 public methods:
279
280 "pipeline"
281 Returns the POE::Component::Pluggable::Pipeline object.
282
283 "plugin_add"
284 Accepts two arguments:
285
286 The alias for the plugin
287 The actual plugin object
288
289 The alias is there for the user to refer to it, as it is possible to
290 have multiple plugins of the same kind active in one
291 POE::Component::Pluggable object.
292
293 This method goes through the pipeline's push() method, which will call
294 "$plugin-"plugin_register($pluggable)>.
295
296 Returns the number of plugins now in the pipeline if plugin was
297 initialized, "undef"/an empty list if not.
298
299 "plugin_del"
300 Accepts one argument:
301
302 The alias for the plugin or the plugin object itself
303
304 This method goes through the pipeline's remove() method, which will
305 call "$plugin-"plugin_unregister($pluggable)>.
306
307 Returns the plugin object if the plugin was removed, "undef"/an empty
308 list if not.
309
310 "plugin_get"
311 Accepts one argument:
312
313 The alias for the plugin
314
315 This method goes through the pipeline's get() method.
316
317 Returns the plugin object if it was found, "undef"/an empty list if
318 not.
319
320 "plugin_list"
321 Takes no arguments.
322
323 Returns a hashref of plugin objects, keyed on alias, or an empty list
324 if there are no plugins loaded.
325
326 "plugin_order"
327 Takes no arguments.
328
329 Returns an arrayref of plugin objects, in the order which they are
330 encountered in the pipeline.
331
332 "plugin_register"
333 Accepts the following arguments:
334
335 The plugin object
336 The type of the hook (the hook types are specified with _pluggable_init()'s 'types')
337 The event name[s] to watch
338
339 The event names can be as many as possible, or an arrayref. They
340 correspond to the prefixed events and naturally, arbitrary events too.
341
342 You do not need to supply events with the prefix in front of them, just
343 the names.
344
345 It is possible to register for all events by specifying 'all' as an
346 event.
347
348 Returns 1 if everything checked out fine, "undef"/an empty list if
349 something is seriously wrong.
350
351 "plugin_unregister"
352 Accepts the following arguments:
353
354 The plugin object
355 The type of the hook (the hook types are specified with _pluggable_init()'s 'types')
356 The event name[s] to unwatch
357
358 The event names can be as many as possible, or an arrayref. They
359 correspond to the prefixed events and naturally, arbitrary events too.
360
361 You do not need to supply events with the prefix in front of them, just
362 the names.
363
364 It is possible to register for all events by specifying 'all' as an
365 event.
366
367 Returns 1 if all the event name[s] was unregistered, undef if some was
368 not found.
369
371 The basic anatomy of a pluggable plugin is:
372
373 # Import the constants, of course you could provide your own
374 # constants as long as they map correctly.
375 use POE::Component::Pluggable::Constants qw( :ALL );
376
377 # Our constructor
378 sub new {
379 ...
380 }
381
382 # Required entry point for pluggable plugins
383 sub plugin_register {
384 my($self, $pluggable) = @_;
385
386 # Register events we are interested in
387 $pluggable->plugin_register($self, 'SERVER', qw(something whatever));
388
389 # Return success
390 return 1;
391 }
392
393 # Required exit point for pluggable
394 sub plugin_unregister {
395 my($self, $pluggable) = @_;
396
397 # Pluggable will automatically unregister events for the plugin
398
399 # Do some cleanup...
400
401 # Return success
402 return 1;
403 }
404
405 sub _default {
406 my($self, $pluggable, $event) = splice @_, 0, 3;
407
408 print "Default called for $event\n";
409
410 # Return an exit code
411 return PLUGIN_EAT_NONE;
412 }
413
414 As shown in the example above, a plugin's "_default" subroutine (if
415 present) is called if the plugin receives an event for which it has no
416 handler.
417
418 The special exit code CONSTANTS are documented in
419 POE::Component::Pluggable::Constants. You could provide your own as
420 long as the values match up, though.
421
423 Better documentation >:]
424
426 APOCAL for writing the original POE::Component::IRC plugin system.
427
428 japhy for writing POE::Component::IRC::Pipeline which improved on it.
429
430 All the happy chappies who have contributed to POE::Component::IRC over
431 the years (yes, it has been years) refining and tweaking the plugin
432 system.
433
434 The initial idea was heavily borrowed from X-Chat, BIG thanks go out to
435 the genius that came up with the EAT_* system :)
436
438 POE::Component::IRC
439
440 POE::Component::Pluggable::Pipeline
441
442 Both POE::Component::Client::NNTP and POE::Component::Server::NNTP use
443 this module as a base, examination of their source may yield further
444 understanding.
445
447 • Chris Williams <chris@bingosnet.co.uk>
448
449 • Apocalypse <perl@0ne.us>
450
451 • Hinrik Örn Sigurðsson
452
453 • Jeff Pinyan
454
456 This software is copyright (c) 2017 by Chris Williams.
457
458 This is free software; you can redistribute it and/or modify it under
459 the same terms as the Perl 5 programming language system itself.
460
461
462
463perl v5.38.0 2023-07-21 POE::Component::Pluggable(3)