54c59630311d5d320306882adffc6f4eaa8a12ee
[OpenSRF.git] / src / perl / lib / OpenSRF / DomainObject / oilsMessage.pm
1 package OpenSRF::DomainObject::oilsMessage;
2 use OpenSRF::Utils::JSON;
3 use OpenSRF::AppSession;
4 use OpenSRF::DomainObject::oilsResponse qw/:status/;
5 use OpenSRF::Utils::Logger qw/:level/;
6 use warnings; use strict;
7 use OpenSRF::EX qw/:try/;
8
9 OpenSRF::Utils::JSON->register_class_hint(hint => 'osrfMessage', name => 'OpenSRF::DomainObject::oilsMessage', type => 'hash');
10
11 sub toString {
12         my $self = shift;
13         return OpenSRF::Utils::JSON->perl2JSON($self);
14 }
15
16 sub new {
17         my $self = shift;
18         my $class = ref($self) || $self;
19         my %args = @_;
20         return bless \%args => $class;
21 }
22
23
24 =head1 NAME
25
26 OpenSRF::DomainObject::oilsMessage
27
28 =head1
29
30 use OpenSRF::DomainObject::oilsMessage;
31
32 my $msg = OpenSRF::DomainObject::oilsMessage->new( type => 'CONNECT' );
33
34 $msg->payload( $domain_object );
35
36 =head1 ABSTRACT
37
38 OpenSRF::DomainObject::oilsMessage is used internally to wrap data sent
39 between client and server.  It provides the structure needed to authenticate
40 session data, and also provides the logic needed to unwrap session data and 
41 pass this information along to the Application Layer.
42
43 =cut
44
45 my $log = 'OpenSRF::Utils::Logger';
46
47 =head1 METHODS
48
49 =head2 OpenSRF::DomainObject::oilsMessage->type( [$new_type] )
50
51 =over 4
52
53 Used to specify the type of message.  One of
54 B<CONNECT, REQUEST, RESULT, STATUS, ERROR, or DISCONNECT>.
55
56 =back
57
58 =cut
59
60 sub type {
61         my $self = shift;
62         my $val = shift;
63         $self->{type} = $val if (defined $val);
64         return $self->{type};
65 }
66
67 =head2 OpenSRF::DomainObject::oilsMessage->api_level( [$new_api_level] )
68
69 =over 4
70
71 Used to specify the api_level of message.  Currently, only api_level C<1> is
72 supported.  This will be used to check that messages are well-formed, and as
73 a hint to the Application as to which version of a method should fulfill a
74 REQUEST message.
75
76 =back
77
78 =cut
79
80 sub api_level {
81         my $self = shift;
82         my $val = shift;
83         $self->{api_level} = $val if (defined $val);
84         return $self->{api_level};
85 }
86
87 =head2 OpenSRF::DomainObject::oilsMessage->sender_locale( [$locale] );
88
89 =over 4
90
91 Sets or gets the current message locale hint.  Useful for telling the
92 server how you see the world.
93
94 =back
95
96 =cut
97
98 sub sender_locale {
99         my $self = shift;
100         my $val = shift;
101         $self->{locale} = $val if (defined $val);
102         return $self->{locale};
103 }
104
105 =head2 OpenSRF::DomainObject::oilsMessage->sender_ingress( [$ingress] );
106
107 =over 4
108
109 Sets or gets the current message ingress.  Useful for telling the
110 server how you entered the opensrf network.
111
112 =back
113
114 =cut
115
116 sub sender_ingress {
117         my $self = shift;
118         my $val = shift;
119         $self->{ingress} = $val if $val;
120         return $self->{ingress};
121 }
122
123 =head2 OpenSRF::DomainObject::oilsMessage->threadTrace( [$new_threadTrace] );
124
125 =over 4
126
127 Sets or gets the current message sequence identifier, or thread trace number,
128 for a message.  Useful as a debugging aid, but that's about it.
129
130 =back
131
132 =cut
133
134 sub threadTrace {
135         my $self = shift;
136         my $val = shift;
137         $self->{threadTrace} = $val if (defined $val);
138         return $self->{threadTrace};
139 }
140
141 =head2 OpenSRF::DomainObject::oilsMessage->update_threadTrace
142
143 =over 4
144
145 Increments the threadTrace component of a message.  This is automatic when
146 using the normal session processing stack.
147
148 =back
149
150 =cut
151
152 sub update_threadTrace {
153         my $self = shift;
154         my $tT = $self->threadTrace;
155
156         $tT ||= 0;
157         $tT++;
158
159         $log->debug("Setting threadTrace to $tT",DEBUG);
160
161         $self->threadTrace($tT);
162
163         return $tT;
164 }
165
166 =head2 OpenSRF::DomainObject::oilsMessage->payload( [$new_payload] )
167
168 =over 4
169
170 Sets or gets the payload of a message.  This should be exactly one object
171 of (sub)type domainObject or domainObjectCollection.
172
173 =back
174
175 =cut
176
177 sub payload {
178         my $self = shift;
179         my $val = shift;
180         $self->{payload} = $val if (defined $val);
181         return $self->{payload};
182 }
183
184 =head2 OpenSRF::DomainObject::oilsMessage->handler( $session_id )
185
186 =over 4
187
188 Used by the message processing stack to set session state information from the current
189 message, and then sends control (via the payload) to the Application layer.
190
191 =back
192
193 =cut
194
195 sub handler {
196         my $self = shift;
197         my $session = shift;
198
199         my $mtype = $self->type;
200         my $locale = $self->sender_locale || '';
201         my $ingress = $self->sender_ingress || '';
202         my $api_level = $self->api_level || 1;
203         my $tT = $self->threadTrace;
204
205     $log->debug("Message locale is $locale; ingress = $ingress", DEBUG);
206
207         $session->last_message_type($mtype);
208         $session->last_message_api_level($api_level);
209         $session->last_threadTrace($tT);
210         $session->session_locale($locale);
211
212         $log->debug(" Received api_level => [$api_level], MType => [$mtype], ".
213                         "from [".$session->remote_id."], threadTrace[".$self->threadTrace."]");
214
215         my $val;
216         if ( $session->endpoint == $session->SERVER() ) {
217                 $val = $self->do_server( $session, $mtype, $api_level, $tT );
218
219         } elsif ($session->endpoint == $session->CLIENT()) {
220                 $val = $self->do_client( $session, $mtype, $api_level, $tT );
221         }
222
223         if( $val ) {
224                 return OpenSRF::Application->handler($session, $self->payload);
225         } else {
226                 $log->debug("Request was handled internally", DEBUG);
227         }
228
229         return 1;
230
231 }
232
233
234
235 # handle server side message processing
236
237 # !!! Returning 0 means that we don't want to pass ourselves up to the message layer !!!
238 sub do_server {
239         my( $self, $session, $mtype, $api_level, $tT ) = @_;
240
241         # A Server should never receive STATUS or RESULT messages.  If so, we drop them.
242         # This is to keep STATUS/RESULT's from dead client sessions from creating new server
243         # sessions which send mangled session exceptions to backends for messages 
244         # that they are not aware of any more.
245     if( $mtype eq 'STATUS' or $mtype eq 'RESULT' ) { return 0; }
246
247         
248         if ($mtype eq 'DISCONNECT') {
249                 $session->disconnect;
250                 $session->kill_me;
251                 return 0;
252         }
253
254         if ($session->state == $session->CONNECTING()) {
255
256                 if($mtype ne "CONNECT" and $session->stateless) {
257                         return 1; #pass the message up the stack
258                 }
259
260                 # the transport layer thinks this is a new connection. is it?
261                 unless ($mtype eq 'CONNECT') {
262                         $log->error("Connection seems to be mangled: Got $mtype instead of CONNECT");
263
264                         my $res = OpenSRF::DomainObject::oilsBrokenSession->new(
265                                         status => "Connection seems to be mangled: Got $mtype instead of CONNECT",
266                         );
267
268                         $session->status($res);
269                         $session->kill_me;
270                         return 0;
271
272                 }
273                 
274                 my $res = OpenSRF::DomainObject::oilsConnectStatus->new;
275                 $session->status($res);
276                 $session->state( $session->CONNECTED );
277
278                 return 0;
279         }
280
281
282         return 1;
283
284 }
285
286
287 # Handle client side message processing. Return 1 when the the message should be pushed
288 # up to the application layer.  return 0 otherwise.
289 sub do_client {
290
291         my( $self, $session , $mtype, $api_level, $tT) = @_;
292
293
294         if ($mtype eq 'STATUS') {
295
296                 if ($self->payload->statusCode == STATUS_OK) {
297                         $session->state($session->CONNECTED);
298                         $log->debug("We connected successfully to ".$session->app);
299                         return 0;
300                 }
301
302                 if ($self->payload->statusCode == STATUS_TIMEOUT) {
303                         $session->state( $session->DISCONNECTED );
304                         $session->reset;
305                         $session->connect;
306                         $session->push_resend( $session->app_request($self->threadTrace) );
307                         $log->debug("Disconnected because of timeout");
308                         return 0;
309
310                 } elsif ($self->payload->statusCode == STATUS_REDIRECTED) {
311                         $session->state( $session->DISCONNECTED );
312                         $session->reset;
313                         $session->connect;
314                         $session->push_resend( $session->app_request($self->threadTrace) );
315                         $log->debug("Disconnected because of redirect", WARN);
316                         return 0;
317
318                 } elsif ($self->payload->statusCode == STATUS_EXPFAILED) {
319                         $session->state( $session->DISCONNECTED );
320                         $log->debug("Disconnected because of mangled session", WARN);
321                         $session->reset;
322                         $session->push_resend( $session->app_request($self->threadTrace) );
323                         return 0;
324
325                 } elsif ($self->payload->statusCode == STATUS_CONTINUE) {
326                         $session->reset_request_timeout($self->threadTrace);
327                         return 0;
328
329                 } elsif ($self->payload->statusCode == STATUS_COMPLETE) {
330                         my $req = $session->app_request($self->threadTrace);
331                         $req->complete(1) if ($req);
332                         return 0;
333                 }
334
335                 # add more STATUS handling code here (as 'elsif's), for Message layer status stuff
336
337                 #$session->state( $session->DISCONNECTED() );
338                 #$session->reset;
339
340         } elsif ($session->state == $session->CONNECTING()) {
341                 # This should be changed to check the type of response (is it a connectException?, etc.)
342         }
343
344         if( $self->payload and $self->payload->isa( "ERROR" ) ) { 
345                 if ($session->raise_remote_errors) {
346                         $self->payload->throw();
347                 }
348         }
349
350         $log->debug("oilsMessage passing to Application: " . $self->type." : ".$session->remote_id );
351
352         return 1;
353
354 }
355
356 1;