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