Commit | Line | Data |
532cc23b |
1 | package stemmaweb::Controller::Stemweb; |
2 | use Moose; |
3 | use namespace::autoclean; |
ed0ce314 |
4 | use Encode qw/ decode_utf8 /; |
5 | use JSON qw/ decode_json encode_json from_json /; |
70744367 |
6 | use LWP::UserAgent; |
532cc23b |
7 | use Safe::Isa; |
8 | use TryCatch; |
70744367 |
9 | use URI; |
532cc23b |
10 | |
11 | BEGIN { extends 'Catalyst::Controller' } |
12 | |
70744367 |
13 | ## TODO Move the /algorithms/available function to the Stemweb module |
14 | my $STEMWEB_BASE_URL = 'http://slinkola.users.cs.helsinki.fi'; |
15 | |
532cc23b |
16 | =head1 NAME |
17 | |
18 | stemmaweb::Controller::Stemweb - Client listener for Stemweb results |
19 | |
20 | =head1 DESCRIPTION |
21 | |
22 | This is a client listener for the Stemweb API as implemented by the protocol defined at |
23 | L<https://docs.google.com/document/d/1aNYGAo1v1WPDZi6LXZ30FJSMJwF8RQPYbOkKqHdCZEc/pub>. |
24 | |
25 | =head1 METHODS |
26 | |
27 | =head2 result |
28 | |
29 | POST stemweb/result |
30 | Content-Type: application/json |
31 | (On success): |
32 | { job_id: <ID number> |
33 | status: 0 |
34 | format: <format> |
35 | result: <data> } |
36 | (On failure): |
37 | { jobid: <ID number> |
38 | status: >1 |
39 | result: <error message> } |
40 | |
41 | Used by the Stemweb server to notify us that one or more stemma graphs |
42 | has been calculated in response to an earlier request. |
43 | |
44 | =cut |
45 | |
46 | sub result :Local :Args(0) { |
47 | my( $self, $c ) = @_; |
48 | if( $c->request->method eq 'POST' ) { |
49 | # TODO: Verify the sender! |
50 | my $answer; |
51 | if( ref( $c->request->body ) eq 'File::Temp' ) { |
52 | # Read in the file and parse that. |
53 | open( POSTDATA, $c->request->body ) or die "Failed to open post data file"; |
54 | binmode( POSTDATA, ':utf8' ); |
55 | # JSON should be all one line |
56 | my $pdata = <POSTDATA>; |
57 | chomp $pdata; |
58 | close POSTDATA; |
59 | $answer = from_json( $pdata ); |
60 | } else { |
61 | $answer = from_json( $c->request->body ); |
62 | } |
63 | # Find a tradition with the defined Stemweb job ID. |
64 | # TODO: Maybe get Stemweb to pass back the tradition ID... |
65 | my $m = $c->model('Directory'); |
66 | my @traditions; |
67 | $m->scan( sub{ push( @traditions, $_[0] ) |
68 | if $_[0]->$_isa('Text::Tradition') |
69 | && $_[0]->has_stemweb_jobid |
70 | && $_[0]->stemweb_jobid eq $answer->{job_id}; |
71 | } ); |
72 | if( @traditions == 1 ) { |
73 | my $tradition = shift @traditions; |
74 | if( $answer->{status} == 0 ) { |
75 | try { |
76 | $tradition->record_stemweb_result( $answer ); |
77 | $m->save( $tradition ); |
78 | } catch( Text::Tradition::Error $e ) { |
79 | return _json_error( $c, 500, $e->message ); |
80 | } catch { |
81 | return _json_error( $c, 500, $@ ); |
82 | } |
83 | # If we got here, success! |
84 | $c->stash->{'result'} = { 'status' => 'success' }; |
85 | $c->forward('View::JSON'); |
86 | } else { |
87 | return _json_error( $c, 500, |
88 | "Stemweb failure not handled: " . $answer->{result} ); |
89 | } |
90 | } elsif( @traditions ) { |
91 | return _json_error( $c, 500, |
92 | "Multiple traditions with Stemweb job ID " . $answer->{job_id} . "!" ); |
93 | } else { |
94 | return _json_error( $c, 400, |
95 | "No tradition found with Stemweb job ID " . $answer->{job_id} ); |
96 | } |
97 | } else { |
98 | return _json_error( $c, 403, 'Please use POST!' ); |
99 | } |
100 | } |
101 | |
70744367 |
102 | =head2 request |
103 | |
104 | GET stemweb/request/? |
105 | tradition=<tradition ID> & |
106 | algorithm=<algorithm ID> & |
107 | [<algorithm parameters>] |
108 | |
109 | Send a request for the given tradition with the given parameters to Stemweb. |
110 | Processes and returns the JSON response given by the Stemweb server. |
111 | |
112 | =cut |
113 | |
114 | sub request :Local :Args(0) { |
115 | my( $self, $c ) = @_; |
116 | # Look up the relevant tradition and check permissions. |
117 | my $reqparams = $c->req->params; |
118 | my $tid = delete $reqparams->{tradition}; |
119 | my $t = $c->model('Directory')->tradition( $tid ); |
120 | my $ok = _check_permission( $c, $t ); |
121 | return unless $ok; |
122 | return( _json_error( $c, 403, |
123 | 'You do not have permission to update stemmata for this tradition' ) ) |
124 | unless $ok eq 'full'; |
125 | |
126 | # Form the request for Stemweb. |
127 | my $algorithm = delete $reqparams->{algorithm}; |
128 | my $return_uri = URI->new( $c->uri_for( '/stemweb/result' ) ); |
129 | my $stemweb_request = { |
130 | return_path => $return_uri->path, |
131 | return_host => $return_uri->host_port, |
132 | data => $t->collation->as_tsv, |
ed0ce314 |
133 | userid => $t->user->email, |
70744367 |
134 | parameters => $reqparams }; |
135 | |
136 | # Call to the appropriate URL with the request parameters. |
ed0ce314 |
137 | $DB::single = 1; |
70744367 |
138 | my $ua = LWP::UserAgent->new(); |
139 | my $resp = $ua->post( $STEMWEB_BASE_URL . "/algorithms/process/$algorithm/", |
140 | 'Content-Type' => 'application/json; charset=utf-8', |
141 | 'Content' => encode_json( $stemweb_request ) ); |
142 | if( $resp->is_success ) { |
143 | # Process it |
ed0ce314 |
144 | $c->log->debug( 'Got a response from the server: ' |
145 | . decode_utf8( $stemweb_response ) ); |
70744367 |
146 | my $stemweb_response = decode_json( $resp->content ); |
147 | try { |
148 | $t->set_stemweb_jobid( $stemweb_response->{jobid} ); |
149 | } catch( Text::Tradition::Error $e ) { |
150 | return _json_error( $c, 429, $e->message ); |
151 | } |
152 | $c->model('Directory')->save( $t ); |
153 | $c->stash->{'result'} = $stemweb_response; |
154 | $c->forward('View::JSON'); |
155 | } elsif( $resp->code == 500 && $resp->header('Client-Warning') |
156 | && $resp->header('Client-Warning') eq 'Internal response' ) { |
157 | # The server was unavailable. |
158 | return _json_error( $c, 503, "The Stemweb server is currently unreachable." ); |
159 | } else { |
ed0ce314 |
160 | return _json_error( $c, 500, "Stemweb error: " . $resp->code . " / " |
70744367 |
161 | . $resp->content ); |
162 | } |
163 | } |
164 | |
165 | # Helper to check what permission, if any, the active user has for |
166 | # the given tradition |
167 | sub _check_permission { |
168 | my( $c, $tradition ) = @_; |
169 | my $user = $c->user_exists ? $c->user->get_object : undef; |
170 | if( $user ) { |
171 | return 'full' if ( $user->is_admin || |
172 | ( $tradition->has_user && $tradition->user->id eq $user->id ) ); |
173 | } |
174 | # Text doesn't belong to us, so maybe it's public? |
175 | return 'readonly' if $tradition->public; |
176 | |
177 | # ...nope. Forbidden! |
178 | return _json_error( $c, 403, 'You do not have permission to view this tradition.' ); |
179 | } |
180 | |
532cc23b |
181 | # Helper to throw a JSON exception |
182 | sub _json_error { |
183 | my( $c, $code, $errmsg ) = @_; |
184 | $c->response->status( $code ); |
185 | $c->stash->{'result'} = { 'error' => $errmsg }; |
186 | $c->forward('View::JSON'); |
187 | return 0; |
188 | } |
189 | |
ed0ce314 |
190 | 1; |