Why not adopt me?
NAME
CatalystX::ConsumesJMS - role for components providing Catalyst actions consuming messages
VERSION
version 1.02
SYNOPSIS
package MyApp::Base::Stuff;
use Moose;
extends 'Catalyst::Component';
with 'CatalystX::ConsumesJMS';
sub _kind_name {'Stuff'}
sub _wrap_code {
my ($self,$c,$destination_name,$msg_type,$route) = @_;
my $code = $route->{code};
my $extra_config = $route->{extra_config};
return sub {
my ($controller,$ctx) = @_;
my $message = $ctx->req->data;
$self->$code($message);
}
}
Then:
package MyApp::Stuff::One;
use Moose;
extends 'MyApp::Base::Stuff';
sub routes {
return {
my_input_destination => {
my_message_type => {
code => \&my_consume_method,
extra_config => $whatever,
},
...
},
...
}
}
sub my_consume_method {
my ($self,$message) = @_;
# do something
}
Also, remember to tell Catalyst to load your Stuff
components:
<setup_components>
search_extra [ ::Stuff ]
</setup_components>
DESCRIPTION
This role is to be used to define base classes for your Catalyst-based JMS / STOMP consumer applications. It's not to be consumed directly by application components.
Routing
Subclasses of your component base specify which messages they are interested in, by writing a routes
sub, see the synopsis for an example.
They can specify as many destinations and message types as they want / need, and they can re-use the code
values as many times as needed.
The main limitation is that you can't have two components using the exact same destination / type pair (even if they derive from different base classes!). If you do, the results are undefined.
It is possible to alter the destination name via configuration, like:
<Stuff::One>
<routes_map>
my_input_destination the_actual_destination_name
</routes_map>
</Stuff::One>
You can also do this:
<Stuff::One>
<routes_map>
my_input_destination the_actual_destination_name
my_input_destination another_destination_name
</routes_map>
</Stuff::One>
to get the consumer to consume from two different destinations without altering the code.
The "code"
The hashref specified by each destination / type pair will be passed to the "_wrap_code" function (that the consuming class has to provide), and the coderef returned will be installed as the action to invoke when a message of that type is received from that destination.
The action, like all Catalyst actions, will be invoked passing:
the controller instance (you should rarely need this)
the Catalyst application context
You can do whatever you need in this coderef, but the synopsis gives a generally useful idea. You can find more examples of use at https://github.com/dakkar/CatalystX-StompSampleApps
Required methods
_kind_name
As in the synopsis, this should return a string that, in the names of the classes deriving from the consuming class, separates the "application name" from the "component name".
These names are mostly used to access the configuration.
_wrap_code
This method is called with:
the Catalyst application as passed to
register_actions
the destination name
the message type
the value from the
routes
corresponding to the destination name and message type slot (see "Routing" above)
The coderef returned will be invoked as a Catalyst action for each received message, which means it will get:
the controller instance (you should rarely need this)
the Catalyst application context
You can get the de-serialized message by calling $c->req->data
. The JMS headers will most probably be in $c->req->env
(or $c->engine->env
for older Catalyst), all keys namespaced by prefixing them with jms.
. So to get all JMS headers you could do:
my $psgi_env = $c->req->can('env')
? $c->req->env
: $c->engine->env;
my %headers = map { s/^jms\.//r, $psgi_env->{$_} }
grep { /^jms\./ } keys $psgi_env;
You can set the message to serialise in the response by setting $c->stash->{message}
, and the headers by calling $c->res->header
(yes, incoming and outgoing data are handled asymmetrically. Sorry.)
Implementation Details
HERE BE DRAGONS.
This role should be consumed by sub-classes of Catalyst::Component
.
The consuming class is supposed to be used as a base class for application components (see the "SYNOPSIS", and make sure to tell Catalyst to load them!).
Since these components won't be in the normal Model/View/Controller namespaces, we need to modify the COMPONENT
method to pick up the correct configuration options. This is were "_kind_name" is used.
We hijack the expand_modules
method to generate various bits of Catalyst code on the fly.
If the component has a configuration entry enabled
with a false value, it is ignored, thus disabling it completely.
We generate a controller package for each destination, by calling "_generate_controller_package", and we add an after
method modifier to its register_actions
method inherited from Catalyst::Controller
, to create the actions for each message type. The modifier is generated by calling "_generate_register_action_modifier".
_generate_controller_package
my $pkg = $self->_generate_controller_package(
$appname,$destination,
$config,$route);
Generates a controller package, inheriting from whatever "_controller_base_classes" returns, called ${appname}::Controller::${destination_name}
. Any roles returned by "_controller_roles" are applied to the controller.
Inside the controller, we set the namespace
config slot to the destination name.
_controller_base_classes
List (not arrayref!) of class names that the controllers generated by "_generate_controller_package" should inherit from. Defaults to 'Catalyst::Controller::JMS'
.
_controller_roles
List (not arrayref!) of role names that should be applied to the controllers created by "_generate_controller_package". Defaults to the empty list.
_generate_register_action_modifier
my $modifier = $self->_generate_register_action_modifier(
$appname,$destination,
$controller_pkg,
$config,$route);
Returns a coderef to be installed as an after
method modifier to the register_actions
method. The coderef will register each action with the Catalyst dispatcher. Each action will have the attribute MessageTarget
, see Catalyst::Controller::JMS.
Each action's code is obtained by calling "_wrap_code".
AUTHOR
Gianni Ceccarelli <gianni.ceccarelli@net-a-porter.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Net-a-porter.com.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.