Why not adopt me?
NAME
CatalystX::ConsumesJMS - role for components providing Catalyst actions consuming messages
VERSION
version 1.0
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>
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_actionsthe destination name
the message type
the value from the
routescorresponding 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.