1package RPC::ExtDirect::EventProvider;
2
3use strict;
4use warnings;
5no  warnings 'uninitialized';           ## no critic
6
7use RPC::ExtDirect::Util::Accessor;
8use RPC::ExtDirect::Config;
9use RPC::ExtDirect;
10use RPC::ExtDirect::NoEvents;
11
12### PACKAGE GLOBAL VARIABLE ###
13#
14# Turn this on for debugging.
15#
16# DEPRECATED. Use `debug_eventprovider` Config option instead.
17# See RPC::ExtDirect::Config.
18#
19
20our $DEBUG;
21
22### PACKAGE GLOBAL VARIABLE ###
23#
24# Set Serializer class name so it could be configured
25#
26# DEPRECATED. Use `serializer_class_eventprovider` or `serializer_class`
27# Config options instead.
28#
29
30our $SERIALIZER_CLASS;
31
32### PACKAGE GLOBAL VARIABLE ###
33#
34# DEPRECATED. This option did nothing in previous versions of
35# RPC::ExtDirect library, and is ignored in 3.x+
36#
37
38our $EVENT_CLASS;
39
40### PACKAGE GLOBAL VARIABLE ###
41#
42# Set Request class name so it could be configured
43#
44# DEPRECATED. Use `request_class_eventprovider` Config option instead.
45#
46
47our $REQUEST_CLASS;
48
49### PUBLIC CLASS METHOD (CONSTRUCTOR) ###
50#
51# Create a new EventProvider object with default API and Config
52#
53
54sub new {
55    my ($class, %arg) = @_;
56
57    $arg{config} ||= RPC::ExtDirect::Config->new();
58    $arg{api}    ||= RPC::ExtDirect->get_api();
59
60    return bless { %arg }, $class;
61}
62
63### PUBLIC CLASS/INSTANCE METHOD ###
64#
65# Run all poll handlers in succession, collect the Events returned
66# by them and return serialized representation suitable for passing
67# on to client side.
68#
69# Note that the preferred way to call this method is on the EventProvider
70# object instance, but we support the class-based way for backwards
71# compatibility.
72#
73# Be aware that the only supported way to configure the EventProvider
74# is to pass a Config object to the constructor and then call poll()
75# on the instance.
76#
77
78sub poll {
79    my ($class, $env) = @_;
80
81    my $self = ref($class) ? $class : $class->new();
82
83    my @poll_requests = $self->_get_poll_requests();
84
85    # Even if we have nothing to poll, we must return a stub Event
86    # or client side will throw an unhandled JavaScript exception
87    return $self->_no_events unless @poll_requests;
88
89    # Run all the requests and collect their results
90    my @results = $self->_run_requests($env, \@poll_requests);
91
92    # No events returned by the handlers? We still gotta return something.
93    return $self->_no_events unless @results;
94
95    # Polling results are always JSON; no content type needed
96    my $serialized = $self->_serialize_results(@results);
97
98    # And if serialization fails we have to return something positive
99    return $serialized || $self->_no_events;
100}
101
102### PUBLIC INSTANCE METHODS ###
103#
104# Simple read-write accessors
105#
106
107RPC::ExtDirect::Util::Accessor::mk_accessors(
108    simple => [qw/ api config /],
109);
110
111############## PRIVATE METHODS BELOW ##############
112
113### PRIVATE INSTANCE METHOD ###
114#
115# Return a list of Request::PollHandler objects
116#
117
118sub _get_poll_requests {
119    my ($self) = @_;
120
121    # Compile the list of poll handler Methods
122    my @handlers = $self->api->get_poll_handlers();
123
124    # Now create the corresponding Request objects
125    my @poll_requests;
126    for my $handler ( @handlers ) {
127        my $req = $self->_create_request($handler);
128
129        push @poll_requests, $req if $req;
130    };
131
132    return @poll_requests;
133}
134
135### PRIVATE INSTANCE METHOD ###
136#
137# Create Request off a poll handler
138#
139
140sub _create_request {
141    my ($self, $handler) = @_;
142
143    my $config      = $self->config;
144    my $api         = $self->api;
145    my $action_name = $handler->action;
146    my $method_name = $handler->name;
147
148    my $request_class = $config->request_class_eventprovider;
149
150    eval "require $request_class";
151
152    my $req = $request_class->new({
153        config => $config,
154        api    => $api,
155        action => $action_name,
156        method => $method_name,
157    });
158
159    return $req;
160}
161
162### PRIVATE INSTANCE METHOD ###
163#
164# Run poll requests and collect results
165#
166
167sub _run_requests {
168    my ($self, $env, $requests) = @_;
169
170    # Run the requests
171    $_->run($env) for @$requests;
172
173    # Collect responses
174    my @results = map { $_->result } @$requests;
175
176    return @results;
177}
178
179### PRIVATE CLASS METHOD ###
180#
181# Serialize results
182#
183
184sub _serialize_results {
185    my ($self, @results) = @_;
186
187    # Fortunately, client side does understand more than on event
188    # batched as array
189    my $final_result = @results > 1 ? [ @results ]
190                     :                  $results[0]
191                     ;
192
193    my $config = $self->config;
194    my $api    = $self->api;
195    my $debug  = $config->debug_eventprovider;
196
197    my $serializer_class = $config->serializer_class_eventprovider;
198
199    eval "require $serializer_class";
200
201    my $serializer = $serializer_class->new(
202        config => $config,
203        api    => $api,
204    );
205
206    my $json = eval {
207        $serializer->serialize(
208            mute_exceptions => 1,
209            debug           => $debug,
210            data            => [$final_result],
211        )
212    };
213
214    return $json;
215}
216
217### PRIVATE CLASS METHOD ###
218#
219# Serializes and returns a NoEvents object.
220#
221
222sub _no_events {
223    my ($self) = @_;
224
225    my $config = $self->config;
226    my $api    = $self->api;
227    my $debug  = $config->debug_eventprovider;
228
229    my $serializer_class = $config->serializer_class_eventprovider;
230
231    eval "require $serializer_class";
232
233    my $serializer = $serializer_class->new(
234        config => $config,
235        api    => $api,
236    );
237
238    my $no_events  = RPC::ExtDirect::NoEvents->new();
239    my $result     = $no_events->result();
240
241    # NoEvents result can't blow up, hence no eval
242    my $serialized = $serializer->serialize(
243        mute_exceptions => !1,
244        debug           => $debug,
245        data            => [$result],
246    );
247
248    return $serialized;
249}
250
2511;
252